# ReUI components The 20 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `icon-tile`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. **Rule one: never guess a component's API. Read it first.** Call **`get_component(name)`** for its inline `api` (props + usage, no web fetch), and **share the result's `docsUrl`** (the component's API documentation page) with the user whenever you work with that component's API, so they have the full reference (the `/llms.txt` index is a further fallback). Then call **`get_examples(name)`** to install a worked example and copy real composition. The contracts below are first-try orientation (required props, composition shape, the one gotcha); the inline `api` is the full reference. No single block fits? Compose: search the components you need, read each `get_component`, install a `get_examples` example per component, and adapt. ## data-grid (the flagship - read its API every time) `data-grid` wraps TanStack Table v8. It is NOT a styled `` and does NOT take `data`/`columns` props directly. The contract: - Build a TanStack table instance with `useReactTable(...)` (columns, data, the feature models you need: sorting, pagination, row selection). - Pass that instance to ``. - Compose the body with `DataGridTable` inside `DataGrid`, and enable features through `tableLayout` (e.g. `{ headerSticky: true, columnsResizable: true }`), not ad-hoc classes. - Server-side data uses the documented fetch shape (`recordCount` is the total for pagination). ```tsx const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), // add sorting/pagination/selection models per the API }) ``` Common mistakes: - **Incorrect:** `` - these props do not exist. **Correct:** build a `useReactTable` instance and pass `table={table}` + `recordCount`. - **Incorrect:** a raw `
` / hand-rolled pagination. **Correct:** use `data-grid`; read its API for sticky header, pagination, virtualization, row selection. - **Incorrect:** styling rows/cells with arbitrary classes. **Correct:** drive layout via `tableLayout` and the documented `ColumnMeta` (e.g. `cellClassName`, `headerTitle`). ## event-calendar **Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root. **Shape:** ```tsx ``` **Gotcha:** headless-first: `EventCalendarContent` renders the active view (month/week/day/days/agenda; a resource view activates when `resources` is passed) - there is no per-view JSX to compose. Events are `{ id, title, start, end (exclusive), allDay?, color?, recurrence?, resourceId? }`. Mutations flow through `onEventUpdate`/`canDropEvent` (return `false` to reject); the root needs an explicit height because it is a min-h-0 flex column. ## gantt **Required:** `resources` (the left tree) plus bars via `events`/`defaultEvents` attached by `resourceId`. **Shape:** ```tsx ``` **Gotcha:** bars move along the time axis only (never across rows) and are all-day spans with exclusive `end`; `progress` is 0-100. Scales are `day | week | month | quarter | year`. Zoom control, infinite scroll, summary rollups, and row checkboxes are ON by default - turn off what you do not need. Same `onEventUpdate`/`canDropEvent` commit pipeline as `event-calendar`; the root needs an explicit height. ## kanban **Required:** `value` (`Record`), `onValueChange`, `getItemValue` **Shape:** ```tsx i.id}> {Object.entries(cols).map(([id, items]) => (

{id}

{items.map((i) => ( {i.title} ))}
))}
``` **Gotcha:** state is `Record`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks. ## sortable **Required:** `value` (`T[]`), `onValueChange`, `getItemValue` **Shape:** ```tsx i.id}> {items.map((i) => ( {i.label} ))} ``` **Gotcha:** a flat 1D reorder list (not columns - that is `kanban`). `getItemValue` must return a stable, unique string. Pass `layout="grid"` or `layout="nested"` for non-list layouts. ## filters **Required:** `filters` (`Filter[]`), `fields` (`FilterFieldConfig[]`), `onChange` **Shape:** ```tsx const [filters, setFilters] = useState([ createFilter("priority", "is_any_of", ["low"]), ]) const fields: FilterFieldConfig[] = [ { key: "priority", label: "Priority", type: "multiselect", options: [{ value: "low", label: "Low" }, { value: "high", label: "High" }] }, ] ``` **Gotcha:** always build initial filters with `createFilter(field, operator, values)` - it generates the required `id`. Never hand-construct a `Filter` object. Pairs naturally with `data-grid`. ## date-selector **Required:** none, but wire `onChange` to capture the value. **Shape:** ```tsx const [value, setValue] = useState() ``` **Gotcha:** the value is a structured `DateSelectorValue` (period / operator / start+end dates), NOT a `Date` - never pass a raw `Date`. Use `allowRange={false}` to lock single-date picking. Read `get_component("date-selector")` for the value shape. ## tree **Required:** `tree` (a `@headless-tree/core` instance you construct) **Shape:** ```tsx {tree.getItems().map((item) => ( ))} ``` **Gotcha:** `Tree` is a styled shell - it takes a headless-tree instance via `tree`, NOT `data`/`items` props. Build the instance with `@headless-tree/react`. External API: https://headless-tree.lukasbach.com/ ## stepper **Required:** `StepperItem step` (number), `StepperContent value` (number) **Shape:** ```tsx 1 2 Step 1 content Step 2 content ``` **Gotcha:** steps are 1-indexed. Without `StepperPanel` + `StepperContent` you render the nav trail but no body. Put `StepperSeparator` in every `StepperItem` except the last. ## timeline **Required:** `TimelineItem step` (number) **Shape:** ```tsx March 2024 Project initialized Repo and architecture set up. ``` **Gotcha:** each item needs a unique `step`. `orientation` is `"vertical"` (default) or `"horizontal"`. This is a static event display, not interactive like `stepper`. ## autocomplete **Required:** `items` (array; each item has at least `value`) **Shape:** ```tsx No results found. {(item) => ( {item.label} )} ``` **Gotcha:** `AutocompleteList` takes a render-prop `(item) => ReactNode`, NOT a mapped array of children. External API: https://base-ui.com/react/components/autocomplete ## phone-input **Required:** none, but wire `onChange`. **Shape:** ```tsx ``` **Gotcha:** `value`/`onChange` use an E.164 string (e.g. `"+14155551234"`), not a display-formatted string; `onChange` can fire `undefined`. `defaultCountry` is a 2-letter ISO code. Wraps `react-phone-number-input`. ## number-field **Required:** wrap the controls in `NumberFieldGroup`. **Shape:** ```tsx ``` **Gotcha:** import from `@/components/ui/number-field`. The accessible label goes on `NumberFieldScrubArea`, not `NumberField`. External API: https://base-ui.com/react/components/number-field ## rating **Required:** `rating` (number) **Shape:** ```tsx ``` **Gotcha:** supports decimals (partial stars). Pass `editable` + `onRatingChange` for interactive input; omit both for a read-only display. ## scrollspy **Required:** `targetRef` (the scroll container ref) **Shape:** ```tsx Section 1 Section 2
...
...
``` **Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window). ## frame **Required:** `Frame` > `FramePanel` **Shape:** ```tsx Title Description
Content
Footer
``` **Gotcha:** a structured card shell for tool-like surfaces. `stacked` connects multiple panels with shared borders; `dense` removes panel padding; radius via the `--frame-radius` CSS variable. ## icon-stack **Required:** one child icon **Shape:** ```tsx ``` **Gotcha:** isometric layered artwork for empty states and illustrations; style the inner icon via its own `className`. Mark purely decorative stacks `aria-hidden="true"` and keep the real label in surrounding copy. ## icon-tile **Required:** one child icon **Shape:** ```tsx ``` **Gotcha:** the square container an icon sits in, so every list row, feature card and empty state shares one affordance. `variant`: `outline` (default) | `elevated` (muted fill, raised ring) | `soft` (tinted nested, tone from currentColor) | `solid` (filled tone, contrasting glyph) | `frame` (double container). `soft` and `solid` retint from one text color class (they default to `text-primary`). `size`: `xs | sm | default | lg | xl` (24/32/40/48/64px tile, glyph scales 12/14/16/20/24px). `radius`: `default | full`. Do not set a `size-*` class on the child icon unless you mean to override the tile's glyph size; recolor with `className` on the tile, not the icon. ## alert **Required:** `Alert` > `AlertTitle` **Shape:** ```tsx Security update Enable two-factor authentication. ``` **Gotcha:** shadcn-compatible API. `variant`: `default | destructive | info | success | warning | invert`. The non-default variants use ReUI extended color tokens (`--success`/`--info`/`--warning`/`--invert`), which the install adds. Defer generic alert rules to the shadcn skill. ## badge **Required:** none (text child). **Shape:** ```tsx Success Pill ``` **Gotcha:** shadcn-compatible. Rich `variant` set (solid, `-outline`, `-light` per color), `size` `xs..xl`, `radius` `default | full`. Like `alert`, the color variants rely on ReUI extended tokens. Prefer `Badge` variants over raw color classes for statuses. ## base vs radix - write for the project's base ReUI ships every component in two builds: `base` (Base UI) and `radix` (Radix UI). The install command and name are identical, and the CLI installs the build matching the project. But you must write/adapt code against the **right base**, because their APIs differ. **Detect the base first.** Read `components.json` -> `style` and take the segment before the first `-`: - `"style": "base-nova"` -> **Base UI** - `"style": "radix-nova"` -> **Radix UI** **Then use that base's API.** The deltas mirror shadcn's base-vs-radix split: - Slot/composition: Base UI `render={<… />}` vs Radix `asChild`. - `Select`: Base UI takes `items`; Radix uses `` children. - `ToggleGroup`: Base UI `multiple` boolean vs Radix `type="single" | "multiple"`. The safest path is to **read the installed files and `c-*` examples** - they're already in your base, so reuse their wiring instead of guessing. When `get_component`'s inline `api` or an example shows the other base's shape, translate it to your base (or `validate_usage` to confirm). Defer the generic base/radix mechanics to the shadcn skill.