# ahrowe-ui

A React UI component library with theming, form validation, and a full component suite.

**[Browse the components](https://ahrowe.com/packages/ui/demo/)**, with live props and a theme editor.

## Install

```bash
npm install @ahrowe/ui
```

## Usage

```tsx
import '@ahrowe/ui/style.css';
import { AhroweProvider } from '@ahrowe/ui';

function Root() {
  return (
    <AhroweProvider>
      <App />
    </AhroweProvider>
  );
}
```

`AhroweProvider` is required, once, at the root. It injects the CSS variables the components paint
with, so without it they render unstyled. It also renders the element Modal, Tooltip, Popover, Toast
and Menu portal into, and shows toasts. Two themes ship, `defaultTheme` (dark) and `lightTheme`.
Pass your own to `themes` to replace them. See [docs/AhroweProvider.md](docs/AhroweProvider.md).

## Before you install

Things that might rule the package out.

- **React 19 or newer.** Refs are plain props here, no `forwardRef`, and that only works from 19 on.
- **Modern browsers.** Chrome and Edge 111, Firefox 114, Safari 16.4. Nothing is transpiled below
  that and no polyfill ships. Two details want a newer Firefox: ScrollFade's edge fade eases from
  128 and snaps before it, and OptionPicker rings the whole group from 121. Both still work either
  way. CI only runs Chromium.
- **One stylesheet.** `@ahrowe/ui/style.css` is 180 KB, 35 KB gzipped, and covers everything. There
  is no per-component CSS. It sits in `@layer ahrowe-ui`, so your own unlayered CSS wins no matter
  which loads first.
- **No right to left.** The stylesheets use `margin-left` and friends rather than logical
  properties, so nothing mirrors.
- **0.x.** A minor can break something, a patch does not. The changelog is written by hand and says
  what to change. 1.0 waits until the API stops moving, so pin the minor if that matters to you.
- **Server components.** Every module is `'use client'`. Nothing renders on the server, but you can
  import it into an RSC app without a wrapper.

### Accessibility

Native elements where they exist, ARIA roles and state where they do not. Everything works from
the keyboard. Groups that act as one control (RadioGroup, OptionPicker, Menu, Tree) take a single
tab stop and move with the arrow keys, and `:focus-visible` keeps a ring off mouse clicks.

Modal traps focus, closes on Escape and hands focus back. Popover does not trap, on purpose, but
Escape still returns you to the trigger. Stack overlays and only the top one hears Escape.

Animation respects `prefers-reduced-motion`. The loading spinners and Toast's countdown bar keep
moving, because a frozen one would be lying.

None of this is audited. No axe run in CI, no screen reader matrix, and contrast comes from the
theme you pass rather than from the library.

## Claude Code

The docs ship inside the package and Claude reads them out of `node_modules`. Three ways in.

**The plugin** installs a skill and the MCP server together. The skill costs one line of context
until Claude actually works on UI.

```bash
/plugin marketplace add ahrowe-dev/ahrowe-ui
/plugin install ahrowe-ui@ahrowe
```

For a whole team at once, commit this to the project's `.claude/settings.json` instead:

```json
{
  "extraKnownMarketplaces": {
    "ahrowe": { "source": { "source": "github", "repo": "ahrowe-dev/ahrowe-ui" } }
  },
  "enabledPlugins": [{ "name": "ahrowe-ui", "marketplace": "ahrowe" }]
}
```

**The docs index** is one line in your own `CLAUDE.md`:

```
@node_modules/@ahrowe/ui/docs/AGENTS.md
```

For another agent, a sentence in your `AGENTS.md` does it: `Before using an @ahrowe/ui component,
read node_modules/@ahrowe/ui/docs/AGENTS.md.` (`docs/CLAUDE.md`, the old name, still imports it.)

It holds the rules common to every component and a table pointing at `docs/<Name>.md`, which
Claude opens when it needs one. This one loads in every session, where the plugin's skill only
wakes up for UI work.

**The MCP server** on its own:

```bash
claude mcp add --transport stdio ahrowe-ui --scope project -- npx --no ahrowe-ui-mcp
```

`search_components` finds a component from a description, `get_component_docs` returns one doc and
`list_components` lists them all. Each doc is a resource too, so you can drop one in yourself with
`@ahrowe-ui:ahrowe-ui://docs/Button`. The `ahrowe-ui-mcp` bin comes with the package, so it only
runs where `@ahrowe/ui` is installed.

## Components

<!-- components:start -->

| Component | Description |
|-----------|-------------|
| `Accordion`, `AccordionStyleType`, `ThemeColor` | Collapsible sections for progressive disclosure — FAQs, settings panels, long-form content that users expand on demand |
| `ActionButtons` | Standardised Submit / Cancel button pair for forms, dialogs, and drawer footers |
| `ActionIcon` | Clickable icon button without a label — toolbar actions, table row actions, close buttons, icon-only controls |
| `AhroweProvider` | The one wrapper an app needs, at its root: it applies the theme, sets default props for every component, shows toasts and renders the element that Modal, Tooltip, Popover, Menu and Toast portal into |
| `Alert`, `ThemeColor` | A persistent inline status message, used for form-level errors, page/section banners, empty-config warnings, and "here's what changed" notices |
| `AnimatedLogo` | A self-drawing SVG mark for splash/loading screens, empty states, or anywhere a brand or icon mark should feel alive rather than static |
| `AnimatedText`, `AnimatedTextVariant`, `AnimatedTextUnit` | Decorative text entrance or emphasis effect, for hero headlines, empty-state messages, "premium"/"new" callouts, loading/decoding states |
| `Avatar`, `AvatarGroup` | User or entity image — profile pictures, comment authors, member lists |
| `Badge`, `BadgeStyleType`, `BadgePosition`, `ThemeColor` | Small count or status indicator — unread counts, notification dots, "new" markers |
| `BodyEnd` | Renders children through a React portal, to the end of the app |
| `Breadcrumb` | A trail showing the current page's position within a hierarchy, with links back to each ancestor, such as a file browser path, a category/subcategory drill-down, or a multi-level settings page |
| `Button`, `ButtonStyleType`, `ButtonSize`, `ButtonType`, `ThemeColor` | Primary interactive action trigger |
| `ButtonGroup`, `ButtonGroupOrientation` | Joins a row (or column) of buttons into a single seamless control, merging their borders and rounding only the outer corners |
| `Calendar`, `CalendarView` | Showing and editing your own events over time, in month, week, day and agenda views: bookings, shifts, appointments |
| `Card`, `CardStyleType`, `CardHeader`, `CardMedia`, `CardContent`, `CardActions`, `ThemeColor` | Surface container for grouping related content — list items, profile cards, dashboard tiles, product listings |
| `Carousel` | A slide track for stepping through a set of items one at a time, with arrows, dots, and drag/swipe support, plus optional autoplay |
| `ChatComposer` | The field a chat message is written in, with its send button: under a ChatMessageList, which shows a whole chat put together |
| `ChatMessage`, `ChatMessageStyleType`, `ChatMessageStatus` | One message in a conversation: a messenger bubble, a support agent's reply, an AI assistant's answer |
| `ChatMessageList` | The scrolling conversation of a chat: a messenger, a support widget, an AI assistant |
| `Checkbox` | Boolean toggle input — settings, multi-select lists, accept terms, feature flags |
| `Chip`, `ChipContainer`, `ChipStyleType`, `ThemeColor` | Compact tag, label, or filter token — selected items, applied filters, category badges, user avatars with remove |
| `ColorPicker` | Hex colour selection — theme editors, annotation tools, customisation panels |
| `CommandPalette` | A search-first dialog for running any command or jumping anywhere in the app from the keyboard, usually opened with ⌘K / Ctrl+K |
| `ConfigProvider`, `useLocale`, `Presentation` | Set global **default props** for library components once, near the root of your app, instead of repeating the same prop on every instance |
| `ConfirmModal` | Confirmation dialog before destructive or irreversible actions — delete, archive, send, overwrite |
| `CopyButton` | Icon-only button that copies a value to the clipboard: an invoice number, an ID in a table row, an API key or share link inside an `Input` |
| `DatePicker`, `dateToDay`, `dayToDate`, `dateToTime`, `dayTimeToDate` | Date selection input — booking forms, birth date fields, date range pickers, deadline selectors |
| `Divider`, `DividerTextPosition`, `DividerOrientation` | A rule that separates sections of content — horizontal by default, filling the width of its parent |
| `Drawer` | Inline horizontal panel that collapses and expands by animating its width — side panels, navigation rails, detail/filter panes that slide open beside the main content |
| `DropZone`, `DropZoneReturnType`, `DropZoneStatus` | Drag-and-drop file upload area, optionally click-to-open the native picker |
| `Dropdown`, `Presentation` | Single-value selection from a list — country pickers, status selectors, category filters, any select-style input |
| `EmptyState` | Placeholder shown when a list, table, or search result has no items — empty inboxes, no search matches, freshly-created sections |
| `ErrorBoundary` | Wrap any subtree whose render errors shouldn't take down the rest of the app — a widget, a route, a third-party embed, a list row |
| `Fab` | Floating action button — primary screen action (create, add, compose), optionally with a speed-dial sub-menu |
| `Flip` | Flip a front/back pair of content 180°, like a physical playing card |
| `emptyFloorPlan`, `addWall`, `derivePlan` | The shared data model behind `RoomDrawer` and `RoomViewer` |
| `FormValidator`, `Validators`, `useFormValidator` | Track and validate a single form field's value, dirty/touched state, and errors |
| `useFormValidatorGroup`, `FormValidatorGroup`, `FormValidator`, `Validators` | Manage a whole form — multiple fields, a single submit handler, validate-on-submit, dirty/touched tracking across the form, and mapping server-side errors back onto the right fields |
| `useHotkeys`, `formatHotkey`, `useMediaQuery`, `useCoarsePointer`, `useVisualViewportSize`, `useScrollEdges`, `useSwipeDismiss`, `useOverlay`, `useClipboard`, `tabbablesIn` | Standalone React hooks the library uses internally and publishes for the same jobs in your own code: keyboard shortcuts, media queries, the mobile keyboard's viewport, scroll-edge detection, swipe-to-dismiss, copying to the clipboard, and the Escape / focus-trap / scroll-lock behaviour behind a custom overlay |
| `IconPicker` | Pick one item from a visual icon/image grid — category selectors, emoji-style pickers, avatar selectors, anything where each option is best represented by an icon or image rather than text alone |
| `IdleManager` | Show something only once the user has stopped interacting — a screen saver, a session-timeout warning, an "are you still there?" prompt, an idle dashboard view |
| `Image` | An `<img>` that holds its place while it loads: a skeleton the image's size, or its thumbnail, then the image, and a fallback if it fails |
| `Input`, `InputType` | Text input with floating label, validation state, and type variants |
| `InputDropdown` | Combobox-style input — the user can type to filter and also pick from a dropdown list |
| `InteractableDiv` | Accessible `<div>` that behaves like a button — click and keyboard (Enter/Space) events, with proper ARIA semantics |
| `KanbanBoard`, `KanbanColumn` | Drag-and-drop board with columns — task management, workflow stages, status boards (e.g. Todo / In Progress / Done) |
| `KlipyPicker` | GIF picker backed by the Klipy API — chat reactions, message composers, content authoring tools |
| `Lightbox` | A full-screen viewer for a set of images, opened from thumbnails: swipe, the arrows or the arrow keys move between them, and each one zooms and pans like a `PanZoom` |
| `ListItem` | One row of a list that stands for a record: a customer, an invoice, a delivery note |
| `Loader`, `LoaderType`, `ThemeColor` | A loading indicator for something in progress with no known end: a fetch, a save, a connection |
| `LoadingOverlay`, `Loader`, `LoaderType` | Covering content while it reloads: a table refreshing, a form saving, a panel fetching its next page |
| `Menu`, `menuHotkeys` | A list of actions hanging off a trigger — a row's "more" button, a toolbar overflow, a context menu on a card |
| `Modal` | Full overlay dialog — forms, detail views, confirmations, wizards that need to block the rest of the UI |
| `MultiSelect` | Pick several options from a list |
| `NumberInput` | Numeric input with formatting — prices, quantities, percentages, phone numbers with separators |
| `OptionPicker` | Segmented control for picking exactly one option from a small set — view modes, sort directions, simple tab-like switches |
| `OtpInput`, `OtpInputCharset` | A one-time-passcode / verification-code input, rendered as one box per character |
| `Overscroll` | Mobile-style collapsing header that shrinks as the user scrolls — profile pages, detail screens, anywhere a large hero area should yield space to content on scroll |
| `Pagination` | Move through server-paged results a page at a time — a results list, an admin table, a report |
| `PanZoom` | A zoomable viewport onto one image, SVG or any other content, inline on the page: zoom with buttons, a pinch, Ctrl + wheel or a double tap, then drag to look around |
| `Popover` | Any panel anchored to a trigger: profile cards, quick forms, action menus, pickers, filter dropdowns |
| `ProgressBar` | Visual progress indicator — file uploads, multi-step completion, storage usage, stacked stats (e.g. budget breakdown) |
| `RadioGroup`, `RadioGroupOrientation` | Single-choice selection from a small, always-visible set of options — plan pickers, shipping methods, yes/no/maybe, settings with 2–5 mutually exclusive choices |
| `Rating` | A star rating input or display, for review scores, feedback prompts, and quality indicators |
| `RevealLens` | A two-layer reveal/magnifier effect — an `overlay` covers a `background` by default, and moving the pointer over the component uncovers the `background` in an area around the cursor |
| `Ripple` | Material-style ink ripple effect on any interactive element — buttons, list items, cards, tiles |
| `RoomDrawer` | An interactive floor plan editor |
| `RoomViewer` | Shows a floor plan someone can look at and click, without letting them change it — a building directory, a booking screen, a dashboard beside a room list |
| `ScrollFade`, `ScrollFadeAxis` | A scrollable box whose content dissolves at the edge it is scrolling past, instead of being cut off mid-line against it |
| `ScrollbarProvider` | Applies custom scrollbar styling to a scrollable region — use to keep scrollbar appearance consistent with the theme |
| `SearchInput` | Search field with built-in debouncing and an optional result count line — list filters, search-as-you-type pages |
| `SectionHeader`, `SectionHeaderSize` | A reusable heading block for the top of a page, panel, card, or any section — a title with an optional subtitle, a leading icon or thumbnail, and a trailing actions area |
| `Skeleton`, `SkeletonVariant`, `SkeletonAnimation` | Content-shaped loading placeholder, shown while real content is being fetched — more polished than a Loader for content-heavy pages (lists, cards, profile headers) |
| `Slider` | Let the user pick a number from a continuous or stepped range by dragging a thumb: volume, brightness, price ranges, zoom, quality presets |
| `SplitButton` | A primary action button with a small attached dropdown for closely related secondary actions, e.g. "Save ▾" opening "Save as draft" / "Save and close" |
| `Stepper`, `StepperOrientation`, `StepperStyleType`, `StepperCollapse`, `StepperLabelPlacement`, `ThemeColor` | Step progress indicator for multi-step forms, onboarding flows, and checkout wizards |
| `Sticky` | Pin a piece of content to the top of the viewport once the page scrolls past it — section headers, toolbars, filter bars, table headers |
| `Switch` | Sliding boolean toggle — settings panels, feature flags, preference lists |
| `TabHeader` | A horizontal row of tabs to switch between sibling views or sections — settings panels, dashboards, detail pages |
| `TemplateEditor`, `TemplateEditorPalettePosition`, `parseTemplate` | Authoring an email or message template that mixes prose with placeholders resolved at send time, e.g. `Hello {{receiver.firstName}},` |
| `Textarea` | Multiline text input — comments, descriptions, notes, code snippets, long-form content |
| `ThemeColor` | The `color` prop of every component that takes one: the theme's named colours, or any CSS colour of your own |
| `ThemeProvider`, `useTheme` | Applies a theme to its subtree, injecting its CSS custom properties as inline styles so every component inside can use them |
| `Tilt`, `TiltGroup` | Wrap content that should tilt toward the cursor in 3D |
| `TimeInput` | Time-of-day selection — appointment times, opening hours, shift starts, reminders |
| `Timeline`, `TimelineAlignment`, `ThemeColor` | A vertical list of dated events, connected by a line, for audit logs, activity feeds, order status history, and deploy logs |
| `Timer` | A countdown timer with a depleting ring, add/remove-time and pause/cancel controls, and an imperative ref API |
| `showToast`, `ThemeColor` | Non-blocking notifications — save confirmations, error alerts, info messages, warnings |
| `Tooltip` | Contextual help, info icons, field explanations, error pointers |
| `Tree`, `flattenTree` | A hierarchical list the user can expand, collapse and pick from: categories, nested locations, folder structures, org charts |
| `VirtualList` | Efficiently render large lists or tables — thousands of rows rendered with a virtualised scroll window so only visible rows are in the DOM |
| `WeatherIcon`, `WeatherType` | An animated weather symbol for a forecast, a dashboard widget or a day selector, one per condition a forecast reports |
| `Wizard` | Animated step container for multi-step forms and flows |

<!-- components:end -->

## Services

`FormValidator`, `FormValidatorGroup` and `Validators` are in the table above.
