# 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 ( {children} ); } function MyPage() { const { theme, setTheme } = useTheme(); return (
Content
); } ``` **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"; Default Active Pending Error ``` ### 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:

More content.

}, ]} /> ``` ### statsCard (Stats Card) ```tsx import { StatsCard } from "@raxrai/stylelab-ui"; ``` ### navbar (Navbar) ```tsx import { Navbar } from "@raxrai/stylelab-ui"; // Responsive: logo + items + optional right + optional mobileMenuExtra (hamburger on mobile) MyApp} items={[ { href: "/", label: "Home" }, { href: "/docs", label: "Docs" }, ]} right={} mobileMenuExtra={} /> // Custom layout (your own content)
...
``` ### commandpalette (Commandpalette) ```tsx import { CommandPalette } from "@raxrai/stylelab-ui"; const [open, setOpen] = useState(false); const commands = [ { id: "new", label: "New file", onSelect: () => {} }, { id: "save", label: "Save", shortcut: "⌘S", onSelect: () => {} }, ]; setOpen(false)} commands={commands} placeholder="Type a command..." /> ``` ### dashboardshell (Dashboardshell) ```tsx import { DashboardShell } from "@raxrai/stylelab-ui"; const navItems = [{ href: "/manage", label: "Manage Tests" }]; const logout = () => {}; Admin} >
Dashboard content
``` ### datatable (Datatable) ```tsx import { DataTable } from "@raxrai/stylelab-ui"; const columns = [{ key: "name", header: "Name" }, { key: "value", header: "Value" }]; const data = [ { name: "Row 1", value: "1" }, { name: "Row 2", value: "2" }, ]; ``` ### dropdown (Dropdown) ```tsx import { Dropdown, Button } from "@raxrai/stylelab-ui"; // Default trigger (shows placeholder or selected label) setValue(v)} /> // Custom trigger (your own button or element) Open menu} items={[ { value: "a", label: "Option A" }, { value: "b", label: "Option B" }, ]} onValueChange={(v) => setValue(v)} /> ``` ### dropdownCustomTrigger (Dropdown Custom Trigger) ```tsx import { Dropdown, Button } from "@raxrai/stylelab-ui"; Open menu} items={[ { value: "a", label: "Option A" }, { value: "b", label: "Option B" }, ]} onValueChange={(v) => setValue(v)} /> ``` ### modal (Modal) ```tsx import { Modal } from "@raxrai/stylelab-ui"; const [open, setOpen] = useState(false); setOpen(false)} title="Confirm">

Are you sure?

``` ### pricingcard (Pricingcard) ```tsx import { PricingCard } from "@raxrai/stylelab-ui"; {}} /> ``` ### progressbar (Progressbar) ```tsx import { ProgressBar } from "@raxrai/stylelab-ui"; ```