# grani > Screens built with grani behave alike: the focus comes back where it was, a late answer never replaces a newer search, typed text that failed to add comes back to its field. The interaction patterns of admin apps, and the Vue components that keep them: tables, forms, records and errors. Version 0.1.0. Vue 3.5.2+ and Tailwind CSS 4.1.0+. ## How to use this reference 1. Check the installed grani version (`npm ls grani`); this text is 0.1.0. 2. Choose components from the catalog, or start from the task below. 3. Read a component's page before writing code: its whole example, its API and what it leaves to the app. 4. Use only the documented props, events and slots. 5. When the components make a screen together, read the patterns for what the person does there: focus, errors, waiting, closing. ## Setup Vue 3 components as a plain package: `npm i grani`, then `app.use(createGrani())`. Tailwind 4.1.0 or later: `@import 'tailwindcss'; @import 'grani/style.css';` in the app's stylesheet (the theme and the sources come with it; it styles grani's elements only, and `@import 'grani/base.css';` after it is an optional base for the app's own). Components are imported from `grani` (`import { UiButton } from 'grani'`) or resolved in templates as by `GraniResolver` from `grani/resolver` through unplugin-vue-components. Brand: override the eight `--color-brand*` variables in your own stylesheet, below the imports, in `:root` for the light theme and again in `.dark` for the dark one; never edit the package's files. Shape: the `--radius-*` variables. Gutters: `--space-overlay`, `--space-surface`, `--space-surface-md`, `--space-drawer`. The dark theme is the `dark` class on ``. Guides: https://grani.arseni.work/guide/theming.md and https://grani.arseni.work/guide/dark-mode.md Colors in the app's own markup: the theme's classes, which follow the brand and the dark theme. The page `bg-bg`; a panel `bg-surface`, a quieter one `bg-surface-muted`; words `text-fg`, quieter words `text-fg-muted` (`text-fg-subtle` is for a mark beside them, an icon or a dot, never for words to read: 2.5:1 on white); a line `border-border`; a state `text-danger`, `bg-success-subtle` and their kin for `danger`, `warning`, `success` and `info`; never Tailwind's raw palette (`bg-neutral-50`, `text-gray-500`) or a hex, which stay the same in the dark theme and under any brand. Language: `createGrani({ locale, translate })`. `locale` is a BCP 47 tag, or a ref or getter of one, and drives dates, numbers, money and plural forms; `translate(key, params)` returns the app's text for grani's keys, or nothing (or the key itself) to keep grani's English. `grani/i18n` exports `en` and `ru`, grani's keys with their text, to feed into the app's own i18n, or `translate: (key) => ru[key]` without one. Size: `createGrani({ size: 'xs' | 'sm' | 'md' })` sets the size of every control without a size of its own; `grani.setSize()` changes it later. Zone and money: `createGrani({ timeZone, currency })`, `grani.setTimeZone()`, `grani.setCurrency()`. Links and server values: `createGrani({ link, shared })`. `link` is the component that draws a link to the app's own paths (unset, `RouterLink` when the app has a router, else a plain anchor); `shared` is the values a server wrote into the page with `sharedSnapshot(app)`, so the first render reads the same. Helpers the entry exports: `rangeFor(preset, today)` gives the days of a named period (`{ from, to }`, whole months), `periodBounds(period, { timeZone, locale, min, max })` the moments it covers for a query (`at >= start && at < end`, the last day whole, by the app's clock, a named period counted from today with the filter's window), and `RANGE_PRESETS` the periods in the order a UiDateRange panel lists them, for the app's own query; `formatChartValue(useUiT(), value, format)` writes a value and `parseChartDate(value)` reads a point of a time axis the way the charts do, for a headline or a tooltip of the app's own. Outside a component (a router guard, a timer): `grani.toast` and `grani.confirm`, the instance's own `useToast()` and `useConfirm()`; inside one, `useGrani()` returns the instance. ## Start from the task - A list of records with search, filters, selection and pages: `UiDataTable`. - A record edited in a side panel, with Save: `UiCrudDrawer`. - A field with its label, hint and error: `UiField` around the control (`UiInput`, `UiSelect`, `UiDatePicker`...). - A question before an action that cannot come back: `UiConfirm`, or `useConfirm()`. - The outcome of an action: `useToast()`, with `UiToaster` mounted once. - Settings as rows: `UiList` with `UiListItem`, in a `UiCard`. - A figure over time in a card: `UiChartCard` around `UiAreaChart`, `UiBarChart` or `UiSparkline`. ## Common mistakes - Import components from `grani`; use only the documented entry points (`grani`, `grani/resolver`, `grani/i18n`, `grani/test`, `grani/style.css`, `grani/base.css`, `grani/theme.css` with `grani/classes/.css` per component). - Set up auto-imports (`GraniResolver`) before using components without an import. - With `grani/theme.css`, import `grani/classes/.css` for every component the app uses: a component without its file renders without its styles. Never point `@source` into `node_modules/grani`: Tailwind then scans the whole folder, every component with it. - Mount `UiToaster` once when using `useToast()`, and `UiConfirm` once when using `useConfirm()`. - Under Yarn Plug'n'Play keep grani unpacked, as its package asks (`preferUnplugged`): kept in a zip, its files reach the development server but not the production build, and the components ship unstyled with no warning. - Import Tailwind without `prefix()`: with it the build stays green and the components show unstyled (with `grani/style.css`, `createGrani()` warns in the browser's console in development). - Keep an empty string and `null` apart where a component documents the difference. - Check each component's props in its API; do not borrow prop names from another UI library. - Components do no requests: the app fetches, saves, checks permissions and passes the data and the states in. - Leave no empty list at a dead end: give the step only the app knows, such as adding the first record or creating what was searched for, in UiDataTable's `#empty-actions` (UiTable's `#empty`), beside the table's own Clear search. ## Get started - [Installation](https://grani.arseni.work/guide/installation.md): a new Vite app, an existing project, or every file by hand - [Theming](https://grani.arseni.work/guide/theming.md): what the Theme page writes, where it goes, every color variable and a color of your own - [Dark mode](https://grani.arseni.work/guide/dark-mode.md): the dark class, no flash before the first paint, a System, Light and Dark switch - [Server rendering](https://grani.arseni.work/guide/server-rendering.md): render on a server, hydrate in the browser - [For AI agents](https://grani.arseni.work/guide/ai-agents.md): the llms.txt in the package and a line for your agent's instructions - [Changelog](https://grani.arseni.work/guide/changelog.md): every version and what changed ## Components - [Every component by family](https://grani.arseni.work/components.md): each component and block with its status - [UiAppShell (App shell)](https://grani.arseni.work/components/app-shell.md): Organize an app around a sidebar for navigation and a main area for the page you are working on. - [UiAreaChart (Area chart)](https://grani.arseni.work/components/area-chart.md): Follow values over time as lines and filled areas. Explore each point from the keyboard, with gaps shown as missing data rather than zero. - [UiAspectRatio (Aspect ratio)](https://grani.arseni.work/components/aspect-ratio.md): Reserve space for a photo or video before it loads, so nearby content stays in place. - [UiAvatar (Avatar)](https://grani.arseni.work/components/avatar.md): Recognize a person by their photo, with initials shown while the photo loads or if it is unavailable. - [UiBadge (Badge)](https://grani.arseni.work/components/badge.md): Add a short status or tag beside an item, such as “Draft” or “Paid”. - [UiBarChart (Bar chart)](https://grani.arseni.work/components/bar-chart.md): Compare amounts across periods with grouped or stacked bars and a plan line, with a marker for a period still in progress. - [UiButton (Button)](https://grani.arseni.work/components/button.md): Start an action and see when it is running, with further presses blocked while the button is loading. - [UiCopyButton (Copy button)](https://grani.arseni.work/components/copy-button.md): Copy a value in one press and see whether it reached the clipboard. - [UiButtonGroup (Button group)](https://grani.arseni.work/components/button-group.md): Keep related controls together, such as a main action and its menu or a field and its submit button. - [UiIconButton (Icon button)](https://grani.arseni.work/components/icon-button.md): Offer a compact action, such as Search or Close, with an icon and an accessible name for screen readers. - [UiCalendar (Calendar)](https://grani.arseni.work/components/calendar.md): Choose a day or date range from a month view, using the mouse or keyboard. - [UiCard (Card)](https://grani.arseni.work/components/card.md): Group one part of a page into a card with a title, content and an optional footer. - [UiChartCard (Chart card)](https://grani.arseni.work/components/chart-card.md): Show a chart with its title and key number; the card keeps its space while loading and says when the result is empty or an update failed. - [UiCheckbox (Checkbox)](https://grani.arseni.work/components/checkbox.md): Choose yes or no by clicking the box or its label; the tick shows the value the app accepted. - [UiCodeInput (Code input)](https://grani.arseni.work/components/code-input.md): Enter a one-time code by typing, pasting or, where the browser supports it, autofill from a message, and let the app know when the code is complete. - [UiConfirm (Confirm)](https://grani.arseni.work/components/confirm.md): Check a decision before an action that cannot be undone, and show progress while the confirmed action runs. - [UiCount (Count)](https://grani.arseni.work/components/count.md): Show a compact count beside a label, or a dot when only the presence of an update matters. - [UiCrudDrawer (Crud drawer)](https://grani.arseni.work/components/crud-drawer.md): Edit a record beside its list and open related lists within the same panel. - [UiDataTable (Data table)](https://grani.arseni.work/components/data-table.md): Find and work with records in one place: search and filters above a table with selection and pages, and clear loading and error states. - [UiDatePicker (Date picker)](https://grani.arseni.work/components/date-picker.md): Type a date as your language writes it, or pick it from a calendar or a preset, while the field keeps a steady width. - [UiDateRange (Date range)](https://grani.arseni.work/components/date-range.md): Filter records by a period such as “This month”, which stays current, or choose two dates in either order. - [UiDelta (Delta)](https://grani.arseni.work/components/delta.md): See how much a value changed as a signed percentage, and whether the change is for the better. - [UiDonutChart (Donut chart)](https://grani.arseni.work/components/donut-chart.md): Compare the parts of a total in a ring chart, with the total in the center and each part available from the keyboard. - [UiDrawer (Drawer)](https://grani.arseni.work/components/drawer.md): Read or edit details in a side panel without leaving the list or page behind it. - [UiDropzone (Dropzone)](https://grani.arseni.work/components/dropzone.md): Add files by dragging or browsing; a message names each file that cannot be added and says why. - [UiField (Field)](https://grani.arseni.work/components/field.md): Give a control its label, hint and error message, connected so a screen reader reads them with it. - [UiFilter (Filter)](https://grani.arseni.work/components/filter.md): Open filter choices from a toolbar button and narrow a list by one or more groups of options. - [UiFilterList (Filter list)](https://grani.arseni.work/components/filter-list.md): Narrow results by ticking options that apply at once; a search field appears when there are six or more. - [UiHeader (Header)](https://grani.arseni.work/components/header.md): Introduce a page or section with a title, a short explanation and the actions available there. - [UiIndicator (Indicator)](https://grani.arseni.work/components/indicator.md): Mark an item that needs attention with a count or dot in its corner. - [UiInput (Input)](https://grani.arseni.work/components/input.md): Enter a single line of text, a password or a number in the app’s local format, such as 1 234,50 or 1,234.50. - [UiPasswordField (Password field)](https://grani.arseni.work/components/password-field.md): Set a new password by typing or generating one, check it on screen and copy it; a screen reader confirms the generation without reading the password. - [UiLink (Link)](https://grani.arseni.work/components/link.md): Follow an underlined link in text, marked when it opens a new tab. - [UiList (List)](https://grani.arseni.work/components/list.md): Present related rows in a card, separated by lines so each item is easy to distinguish. - [UiListField (List field)](https://grani.arseni.work/components/list-field.md): Search a list and add entries from one field. Paste a column to add several entries, and recover your text if adding fails. - [UiListItem (List item)](https://grani.arseni.work/components/list-item.md): Show one row with a title and description and an action or value beside it. - [UiLog (Log)](https://grani.arseni.work/components/log.md): Follow a record's history: who changed what and when, with complete old and new values shown together. - [UiMenu (Menu)](https://grani.arseni.work/components/menu.md): Open a menu of available actions, such as exporting records, changing a row or managing your account. - [UiModal (Modal)](https://grani.arseni.work/components/modal.md): Complete a short task in a dialog, such as filling a form or reviewing a record's details. - [UiNoData (No data)](https://grani.arseni.work/components/no-data.md): Understand why an area is empty and what you can do next. - [UiPagination (Pagination)](https://grani.arseni.work/components/pagination.md): Move between pages with Previous, Next and page numbers below a list or table. - [UiPopover (Popover)](https://grani.arseni.work/components/popover.md): Open a small panel of options beside a button while keeping the rest of the page available. - [UiProgress (Progress)](https://grani.arseni.work/components/progress.md): See how much of a task or target is complete when its progress can be measured. - [UiRadioGroup (Radio group)](https://grani.arseni.work/components/radio-group.md): Choose one answer from a short list, cards or a segmented bar; the whole row or card is the click target. - [UiReveal (Reveal)](https://grani.arseni.work/components/reveal.md): Open details where you need them and collapse them again without leaving the page. - [UiScaleInput (Scale input)](https://grani.arseni.work/components/scale-input.md): Give a rating on a short labeled scale and, where clearing is allowed, take it back by choosing the same value again. - [UiSelect (Select)](https://grani.arseni.work/components/select.md): Search for and choose one or more options. Enter selects an option without also submitting the form. - [UiSeparator (Separator)](https://grani.arseni.work/components/separator.md): Separate groups in a menu, toolbar or card with a simple dividing line. - [UiSkeleton (Skeleton)](https://grani.arseni.work/components/skeleton.md): Show where content will appear while it loads, holding its space so the page around it moves less. - [UiSparkline (Sparkline)](https://grani.arseni.work/components/sparkline.md): See a value's recent trend beside the number, with gaps where data is missing. - [UiSpinner (Spinner)](https://grani.arseni.work/components/spinner.md): Show that work is in progress where the surrounding component has no loading state of its own. - [UiStack (Stack)](https://grani.arseni.work/components/stack.md): Arrange related content vertically or horizontally with consistent spacing between groups, headings and sections. - [UiStepper (Stepper)](https://grani.arseni.work/components/stepper.md): Count things with minus and plus, such as pieces in a cart or guests on a booking, or type the number when it is far from the current one. - [UiSwitch (Switch)](https://grani.arseni.work/components/switch.md): Change an on/off setting immediately, returning to the previous position if the app rejects the change. - [UiTable (Table)](https://grani.arseni.work/components/table.md): Compare records in columns, sort and select rows and act on a row, with clear states for loading, empty results and errors. - [UiTableColumns (Table columns)](https://grani.arseni.work/components/table-columns.md): Choose which columns to show and keep that choice for later visits in the same browser. - [UiTableToolbar (Table toolbar)](https://grani.arseni.work/components/table-toolbar.md): Keep search, filters and actions together above the table they affect. - [UiTabs (Tabs)](https://grani.arseni.work/components/tabs.md): Switch between sections of the same screen without showing them all at once. - [UiTextarea (Textarea)](https://grani.arseni.work/components/textarea.md): Write several lines, such as a note, address or biography, in a field that can grow with the text. - [UiTimePicker (Time picker)](https://grani.arseni.work/components/time-picker.md): Choose hours and minutes or type a time such as 2:30p or 1430. A valid time is kept even if it is not among the listed options. - [UiToaster (Toaster)](https://grani.arseni.work/components/toaster.md): See a short message about an action's progress or result, with an action such as Undo when available. - [UiTooltip (Tooltip)](https://grani.arseni.work/components/tooltip.md): Get a short hint on hover, keyboard focus or tap; anything essential stays on the page outside the tooltip. - [UiTruncate (Truncate)](https://grani.arseni.work/components/truncate.md): Keep a long value on one line with an ellipsis, and reveal the full text on hover or tap when it does not fit. ## Patterns - [How admin screens behave](https://grani.arseni.work/patterns.md): 126 interaction patterns, by what the person is doing, with explanations, who implements them and test references where available (111 of them have tests). - [Focus and navigation](https://grani.arseni.work/patterns/opening.md): Where keyboard focus starts, how it moves, and where it returns. - [Closing panels](https://grani.arseni.work/patterns/closing.md): Dismiss nested panels in order, the top one first, and leave the form behind them as it was. - [Forms and input](https://grani.arseni.work/patterns/filling-in.md): Enter values, correct validation errors and submit a form. - [Search and selection](https://grani.arseni.work/patterns/finding.md): Find options, filter rows and navigate long lists. - [Loading and feedback](https://grani.arseni.work/patterns/waiting.md): Show progress, explain failures and confirm actions when needed. - [Working with records](https://grani.arseni.work/patterns/working-the-list.md): Select rows, apply actions and move between records. - [The tests behind the patterns](https://grani.arseni.work/patterns/tests.md): each pattern's tests, named, and grani/test, the checks to run on your own screens ## Optional - [Everything in one file](https://grani.arseni.work/llms-full.txt): every component with its example and API, and the patterns - [Source](https://github.com/arsdmitriev/grani)