# StyleLab UI
> A themeable, accessible React UI library with 8 themes (Minimal, Night, Glass, Neubrutal, Clay, Cyberpunk, Retro, Motion). Built with Tailwind CSS v4, CVA, and Lucide icons. NPM: @raxrai/stylelab-ui. Use this document to understand each component's props, how to use them, and how the library is structured.
## Key information
- [Home](/): Landing and theme preview
- [Gallery](/gallery): Component explorer with live props and theme matrix
- [Grid Playground](/gallery/playground): Compose interfaces on a 12×10 grid
- [Installation & Theming](/docs/installation-and-theming): How to install and switch themes
- [Component Reference](/docs/component-reference): API for all components (web)
- [Changelog](/docs/changelog): Version history
## Quick start (for AI building apps)
**Install and setup**
- `npm install @raxrai/stylelab-ui` (peer: react, react-dom ≥18). Requires Tailwind CSS v4.
- In your global CSS (e.g. `app/globals.css`): `@import "tailwindcss"; @import "@raxrai/stylelab-ui/styles";` — StyleLab must be imported after Tailwind.
- Wrap your app (or the subtree that uses the library) in `ThemeProvider`. All components must be inside a `ThemeProvider` to receive the active theme.
**Minimal app example**
```tsx
import { ThemeProvider, Button, Card, useTheme } from "@raxrai/stylelab-ui";
export default function RootLayout({ children }) {
return (
);
}
```
**ThemeProvider (full props)**
- `defaultTheme?: StyleLabTheme` — initial theme (default `"minimal"`).
- `storageKey?: string` — localStorage key for persisting theme (default `"stylelab-theme"`). Only used when `persistTheme` is true.
- `persistTheme?: boolean` — when `true` (default), theme is restored from localStorage on mount and saved when `setTheme` is called. When `false`, theme is not read from or written to localStorage (fixed to `defaultTheme` unless changed in memory).
**Usage patterns**
- **Controlled vs uncontrolled:** Many components support both (e.g. `value` + `onValueChange` for controlled, `defaultValue` for uncontrolled). Tabs, Slider, Dropdown, Toggle, Calendar follow this pattern.
- **Dropdown (two patterns):** (1) Default trigger — omit `trigger`; the component shows a button with selected label or `placeholder`. (2) Custom trigger — pass `trigger` with your own element (e.g. ``); the same list opens on activation.
- **Theme override:** Many components accept an optional `theme?: StyleLabTheme` prop to render in a different theme than context (e.g. one Card in "cyberpunk" while the app is "minimal").
- **Button as link:** `` — Button is polymorphic; use `as` and anchor props for links.
- **Navbar (two patterns):** (1) Responsive — pass `logo`, `items` (`{ href, label }[]`), optional `right`, and optional `mobileMenuExtra` (e.g. Log out button in the hamburger menu). Mobile gets a hamburger that opens an overlay; the overlay has an opaque theme-aware background and click-outside includes the panel so links and buttons inside the menu work before the menu closes. (2) Custom — pass `children` only for full control of layout.
- **Imports:** Import components, hooks, and types from `@raxrai/stylelab-ui` (e.g. `Button`, `Card`, `useTheme`, `ThemeProvider`, `type StyleLabTheme`). `cn` is exported for composing `className`.
- **Gallery code examples:** The section **Gallery code examples** at the end of this file lists copy-paste snippets from `app/gallery/registry.tsx` (same as the Gallery Code tab). Use it when you need runnable JSX for components that define `exampleCode`.
---
## UI Library Features & API Reference
This document lists the main features of the library and all exported components, hooks, and utilities with their props / options.
---
## Global Features
- **Themes**
- **`StyleLabTheme`**: `"minimal" | "night" | "glass" | "neubrutal" | "clay" | "cyberpunk" | "retro" | "motion"`
- **`THEMES`**: readonly array of all `StyleLabTheme` values.
- **`getThemeClass(theme, component, variant?)`**
- `theme: StyleLabTheme`
- `component: ComponentName`
- `variant?: string`
- Returns the theme-specific class string for the component (and optional variant).
- **`ComponentName`**: `"button" | "input" | "toggle" | "badge" | "slider" | "tabs" | "card" | "bento" | "navbar" | "sidebar" | "sectionHeader" | "modal" | "toast" | "progress" | "tooltip" | "dataTable" | "pricingCard" | "terminal" | "statsCard" | "commandPalette" | "accordion" | "skeleton" | "avatar" | "breadcrumbs" | "alert" | "calendar" | "timeline"`
- **Theming Context**
- **`ThemeProvider`**
- `children: ReactNode`
- `defaultTheme?: StyleLabTheme` — initial theme (default `"minimal"`).
- `storageKey?: string` — localStorage key for persisting theme (default `"stylelab-theme"`). Used only when `persistTheme` is true.
- `persistTheme?: boolean` — when true (default), theme is restored from localStorage on mount and saved when `setTheme` is called. When false, theme is not read from or written to localStorage.
- **`useTheme()`**
- Returns: `{ theme: StyleLabTheme; setTheme(theme: StyleLabTheme): void }`
- **Styling overrides**
- Every component’s root DOM element accepts a **`style`** prop (standard HTML `style`). Consumer-supplied values take precedence when merged with any internal styles.
- Every component’s root merges a **`className`** prop with internal/theme classes (e.g. via `cn()`), with the consumer’s `className` last so Tailwind and custom classes can override theme-based colors and other utilities. This avoids unreadable text when components (e.g. Card, ghost Button) are used inside containers that set their own text color.
- **Utilities**
- **`cn(...inputs: ClassValue[]): string`**
- Tailwind-aware class name merge (clsx + tailwind-merge). Use for component `className` composition.
- **`mergeRefs(...refs)`**
- Combines multiple refs (e.g. forwarded ref + internal ref) into one callback ref.
- **`getNextListIndex(current, direction, length): number`**
- `current: number`
- `direction: "up" | "down"`
- `length: number`
- Returns the next index for list keyboard navigation (wraps at ends).
---
## Testing
- **`npm run test`** — Run all tests (includes smoke tests for every component).
- **`npm run test:coverage`** — Run tests with coverage report.
---
## Hooks
- **`useFocusTrap(active, options?)`**
- `active: boolean`
- `options?: { returnFocus?: boolean }` — default `returnFocus: true`
- Returns: `RefObject` — attach to the container element (e.g. modal).
- **`useClickOutside(ref, handler, options?)`**
- `ref: RefObject | RefObject[]`
- `handler: (e: MouseEvent | TouchEvent) => void`
- `options?: { enabled?: boolean }`
- **`useKeyboardNavigation(options)`**
- `options`: `{
onEscape?: (e: KeyboardEvent) => void;
onArrowDown?: (e: KeyboardEvent) => void;
onArrowUp?: (e: KeyboardEvent) => void;
onArrowLeft?: (e: KeyboardEvent) => void;
onArrowRight?: (e: KeyboardEvent) => void;
enabled?: boolean;
}`
- Registers keydown listeners (capture phase). No return value.
---
## Components
### Accordion
- **`Accordion(props: AccordionProps)`**
- **Usage**
- By default only one panel is open at a time. Set `allowMultiple={true}` to allow multiple panels open (e.g. FAQs where users compare answers).
- **Props**
- `items: AccordionItem[]` **(required)**
- `AccordionItem`: `{ id: string; title: ReactNode; content: ReactNode }`
- `allowMultiple?: boolean`
- `className?: string`
- `theme?: StyleLabTheme`
---
### Alert
- **`Alert`** – `React.forwardRef`
- **Props**
- All standard `HTMLAttributes`
- `intent?: "default" | "info" | "success" | "warning" | "danger"`
- `title?: ReactNode`
- `children?: ReactNode`
- `className?: string`
- `theme?: StyleLabTheme`
- `role` defaults to `"alert"`
---
### Avatar & AvatarGroup
- **`Avatar`** – `React.forwardRef`
- **Props**
- All standard `HTMLAttributes`
- `src?: string | null` — image URL; when null/undefined, fallback or alt-derived initials are used.
- `alt?: string` — accessible description; also used to derive initials when no `fallback`.
- `fallback?: string` — shown when no image (e.g. initials). Overrides derived initials from `alt`.
- `theme?: StyleLabTheme`
- **`AvatarGroup`**
- **Props**
- `children: ReactNode` **(required)** — one or more `Avatar` elements.
- `max?: number` — max avatars to show before +N badge. Default: `4`.
- `className?: string`
- All standard `HTMLAttributes`
- `theme?: StyleLabTheme`
---
### Badge
- **`Badge`** – `React.forwardRef`
- **Props**
- All standard `HTMLAttributes`
- `variant?: "default" | "success" | "warning" | "error"`
- `dot?: boolean`
- `theme?: StyleLabTheme`
- `className?: string` (via HTML attributes / variants)
---
### BentoGrid
- **`BentoGrid`**
- **Props**
- `children: ReactNode`
- `className?: string`
---
### Breadcrumbs
- **`Breadcrumbs(props: BreadcrumbsProps)`**
- **Props**
- `items: BreadcrumbItem[]` **(required)**
- `BreadcrumbItem`: `{ label: ReactNode; href?: string }` — items without `href` render as current page (plain text).
- `separator?: ReactNode` — default is theme-based (e.g. `"/"`, `"›"`, `"→"`).
- `className?: string`
- `theme?: StyleLabTheme`
---
### Button
- **`Button`**
- Polymorphic: renders as `"button"` by default or another element when `as` is passed.
- **Usage**
- **As button:** Default. Use `variant` (primary/secondary/ghost), `size`, `isLoading`, `leftIcon`/`rightIcon`.
- **As link:** Pass `as="a"` and anchor props (e.g. `href`, `target`). Use for in-app navigation that should look like a button.
- **Props (Button-specific)**
- `as?: T`
- `variant?: "primary" | "secondary" | "ghost"`
- `size?: "sm" | "md" | "lg"`
- `loading?: boolean` **(deprecated – use `isLoading`)**
- `isLoading?: boolean`
- `leftIcon?: ReactNode`
- `rightIcon?: ReactNode`
- `className?: string`
- `children?: ReactNode`
- **Underlying element props**
- If `T` is `"a"`: all `AnchorHTMLAttributes`
- Otherwise: all `ButtonHTMLAttributes`
---
### Calendar
- **`Calendar(props: CalendarProps)`**
- **Props**
- `value?: Date | null` — controlled selected date.
- `defaultValue?: Date | null` — uncontrolled initial date.
- `onSelect?: (date: Date) => void`
- `min?: Date` — minimum selectable date.
- `max?: Date` — maximum selectable date.
- `className?: string`
- `theme?: StyleLabTheme`
---
### Card
- **`Card`** – `React.forwardRef`
- **Usage**
- Use optional `header` and `footer` for a three-section card (header, default slot as body, footer). Omit for a single content area. Use `classNames` to target root, header, body, or footer for styling.
- **Props**
- All standard `HTMLAttributes`
- `padding?: "none" | "sm" | "md"`
- `isHoverable?: boolean`
- `header?: ReactNode`
- `footer?: ReactNode`
- `classNames?: { root?: string; header?: string; body?: string; footer?: string }`
- `theme?: StyleLabTheme`
---
### CommandPalette
- **`CommandPalette`**
- **Usage**
- Control visibility with `open` and `onClose`. Typically bound to a shortcut (e.g. ⌘K). Each command needs `id`, `label`, and `onSelect`; optional `shortcut` is shown on the right. List is filterable by typing.
- **Props**
- `open: boolean`
- `onClose: () => void`
- `commands: CommandItem[]`
- `CommandItem`: `{ id: string; label: string; shortcut?: string; onSelect: () => void }`
- `placeholder?: string` — default e.g. `"Type a command..."`
---
### DashboardShell
- **`DashboardShell(props: DashboardShellProps)`**
- **Example**
`Admin}>
Dashboard content
`
- **Props**
- `children: ReactNode`
- `navItems: { href: string; label: string }[]`
- `adminNavItems?: { href: string; label: string }[]`
- `currentPath?: string` — used to highlight the active nav item.
- `userEmail?: string`
- `userRole?: string` — when `"ADMIN"`, `adminNavItems` are shown.
- `onLogout?: () => void`
- `logo?: ReactNode`
- `className?: string`
---
### DataTable
- **`DataTable>`**
- **Props**
- `columns: Column[]`
- `Column`: `{ key: keyof T | string; header: string; render?: (row: T) => ReactNode }`
- `data: T[]`
- `getRowKey?: (row: T, index: number) => string | number`
- `className?: string`
---
### DocumentAccordion
- **`DocumentAccordion(props: DocumentAccordionProps)`**
- **Props**
- `title: ReactNode` **(required)**
- `content?: ReactNode` — rendered as rich text when `pdfUrl` is not provided.
- `pdfUrl?: string` — when provided, renders an inline PDF viewer with an “Open PDF” link.
- `defaultOpen?: boolean`
- `className?: string`
- `theme?: StyleLabTheme`
---
### Dropdown
- **`Dropdown`** — Select menu with two usage patterns.
- **Usage**
- **Default trigger:** Omit `trigger`. The component renders a built-in button that shows the selected item’s label or `placeholder` when nothing is selected. Use `placeholder` and optionally `value` / `defaultValue` and `onValueChange`.
- **Custom trigger:** Pass `trigger` with your own React node (e.g. a button or icon). The same list opens when the user activates the trigger; `placeholder` is only used for the default trigger.
- **Props**
- `trigger?: ReactNode` — custom trigger; if omitted, default trigger shows selected label or `placeholder`
- `placeholder?: string` (default `"Select…"`) — used when no value is selected (default trigger only)
- `items: DropdownItem[]`
- `DropdownItem`: `{ value: string; label: ReactNode; disabled?: boolean }`
- `value?: string`
- `defaultValue?: string`
- `onValueChange?: (value: string) => void`
- `disabled?: boolean` — when true, the dropdown does not open and the trigger is non-interactive
- `className?: string`
- `style?: React.CSSProperties`
- `theme?: StyleLabTheme`
- `classNames?: { trigger?: string; popover?: string; item?: string }`
- **Example (default trigger)**
``
- **Example (custom trigger)**
`Open menu} items={[{ value: "a", label: "Option A" }]} onValueChange={setValue} />`
---
### Flashcard
- **`Flashcard(props: FlashcardProps)`**
- **Props**
- `question: string` **(required)**
- `answer: string` **(required)**
- `className?: string`
- All standard `HTMLAttributes` are supported.
---
### GraphicCard
- **`GraphicCard(props: GraphicCardProps)`**
- **Props**
- `children: ReactNode`
- `className?: string`
- All standard `HTMLAttributes` are supported.
---
### Input
- **`Input`** – `React.forwardRef`
- **Root:** `className` and `style` apply to the root wrapper (the outer div that contains label, input, error, and helper text), not the `` element. Use the wrapper for layout (e.g. `className="max-w-xs"`); use child selectors to style the field (e.g. `className="[&_input]:max-w-full"`).
- **Props**
- All `InputHTMLAttributes` except:
- `size` is reserved for the component’s own size variant.
- `size?: "sm" | "md" | "lg"`
- `label?: string`
- `error?: string`
- `helperText?: string`
- `prefix?: ReactNode`
- `suffix?: ReactNode`
- `theme?: StyleLabTheme`
---
### Modal
- **`Modal`**
- **Usage**
- Control visibility with `open` and `onClose` (e.g. from local state). Use `title` for a header and `size` for width. Children are the body content.
- **Props**
- `open: boolean`
- `onClose: () => void`
- `title?: string`
- `size?: "sm" | "md" | "lg"`
- `className?: string`
- `children?: ReactNode`
- `theme?: StyleLabTheme`
---
### Navbar
- **`Navbar`** — Two usage patterns.
- **Usage**
- **Responsive (logo + items + right + optional mobileMenuExtra):** Pass `logo`, `items` (`{ href, label }[]`), optional `right`, and optional `mobileMenuExtra` (e.g. Log out button in the hamburger menu). Desktop shows horizontal links; mobile shows a hamburger that toggles a menu overlay. The overlay has an opaque theme-aware background; click-outside ref includes the panel so links and buttons in the menu run before the menu closes.
- **Custom layout:** Pass `children` only (no `logo`/`items`). Renders your content inside the navbar container. Use when you need full control of the inner layout.
- **Example**
`MyApp} items={[{ href: "/", label: "Home" }]} right={} mobileMenuExtra={} />`
- **Props**
- `children?: ReactNode` — when provided without `items`, the navbar shows this content as-is (custom layout).
- `logo?: ReactNode` — brand/logo (left). Used when `items` is provided.
- `items?: NavbarItem[]` — nav links. `NavbarItem`: `{ href: string; label: ReactNode }`. When set, desktop shows links; mobile shows hamburger menu.
- `right?: ReactNode` — slot for right side (e.g. theme switcher, user menu).
- `mobileMenuExtra?: ReactNode` — optional content shown only in the mobile hamburger menu (e.g. Log out button). Rendered at the bottom of the overlay; clicking it closes the menu.
- `className?: string`
- `style?: React.CSSProperties`
---
### PricingCard
- **`PricingCard`**
- **Props**
- `tier: Tier` **(required)**
- `Tier`: `{ name: string; price: string; description?: string; features: string[]; cta: string; highlighted?: boolean }`
- `onSelect?: () => void`
- `className?: string`
---
### ProgressBar
- **`ProgressBar`**
- **Accessibility:** Root has `role="progressbar"` with `aria-valuenow`, `aria-valuemin`, `aria-valuemax`. Pass `aria-label` when the bar needs an accessible name (e.g. "Upload progress").
- **Props**
- `value: number` **(required)**
- `max?: number` — default `100`.
- `segmented?: boolean`
- `className?: string`
- `style?: React.CSSProperties`
- `aria-label?: string` — accessible name for the progress bar.
- `trackColor?: string` — override track background (e.g. hex).
- `activeColor?: string` — override fill color (e.g. hex).
---
### SectionHeader
- **`SectionHeader`**
- **Props**
- `title: string` **(required)**
- `subtitle?: string`
- `className?: string`
- `style?: React.CSSProperties`
---
### Sidebar
- **`Sidebar`**
- **Props**
- `children: ReactNode`
- `className?: string`
---
### Skeleton
- **`Skeleton`** – `React.forwardRef`
- **Props**
- All standard `HTMLAttributes`
- `variant?: "rectangle" | "circle" | "text"`
- `className?: string`
- `style?: React.CSSProperties`
- `theme?: StyleLabTheme`
---
### Slider
- **`Slider`** – `React.forwardRef`
- **Props**
- All `InputHTMLAttributes` except:
- `value`, `type`, `onChange`, `size`, `defaultValue` (overridden by controlled API).
- `size?: "sm" | "md" | "lg"`
- `min?: number`
- `max?: number`
- `step?: number`
- `value?: number`
- `defaultValue?: number`
- `onValueChange?: (value: number) => void`
- `theme?: StyleLabTheme`
---
### StatsCard
- **`StatsCard`**
- **Props**
- `label: string`
- `value: string | number`
- `trend?: { direction: "up" | "down"; value: string }`
- `className?: string`
---
### Tabs
- **`Tabs`**
- **Props**
- `items: TabItem[]`
- `TabItem`: `{ label: string; value: string; content: ReactNode }`
- `defaultValue?: string`
- `value?: string`
- `onValueChange?: (value: string) => void`
- `className?: string`
- `theme?: StyleLabTheme`
---
### Terminal
- **`Terminal`**
- **Props**
- `title?: string`
- `children: ReactNode`
- `className?: string`
---
### Timeline
- **`Timeline(props: TimelineProps)`**
- **Props**
- `items: TimelineItem[]` **(required)**
- `TimelineItem`: `{ title: ReactNode; description?: ReactNode; time?: ReactNode }`
- `className?: string`
- `theme?: StyleLabTheme`
---
### Toast
- **`Toast`**
- **Props**
- `message: string` **(required)**
- `onDismiss: () => void` **(required)**
- `className?: string`
- `style?: React.CSSProperties`
---
### Toggle
- **`Toggle`** – `React.forwardRef`
- **Props**
- All `InputHTMLAttributes` except:
- `value`, `type`, `onChange`, `size` (overridden by controlled API).
- `checked?: boolean`
- `defaultChecked?: boolean`
- `onCheckedChange?: (checked: boolean) => void`
- `theme?: StyleLabTheme`
---
### Tooltip
- **`Tooltip`**
- **Props**
- `content: ReactNode`
- `children: ReactNode`
- `placement?: "top" | "bottom" | "left" | "right"`
- `className?: string`
- `theme?: StyleLabTheme`
## Gallery code examples
The following snippets are copied from `exampleCode` in `app/gallery/registry.tsx` (same text as the Gallery **Code** tab). Components without an `exampleCode` entry are not listed here — see the component sections above for full prop docs.
### button (Button)
```tsx
import { Button } from "@raxrai/stylelab-ui";
```
### badge (Badge)
```tsx
import { Badge } from "@raxrai/stylelab-ui";
DefaultActivePendingError
```
### skeleton (Skeleton)
```tsx
import { Skeleton } from "@raxrai/stylelab-ui";
```
### card (Card)
```tsx
import { Card } from "@raxrai/stylelab-ui";
Simple card content
Body content here.
```
### alert (Alert)
```tsx
import { Alert } from "@raxrai/stylelab-ui";
Informational message.Saved successfully.This cannot be undone.Something went wrong.
```
### accordion (Accordion)
```tsx
import { Accordion } from "@raxrai/stylelab-ui";
Content here. },
{ id: "2", title: "Section 2", content: