React
React components that preserve Lumen’s shared visual contract, paired with focused hooks for dialogs, menus, selects, and other interactive patterns.
- Surface
- Adapter
- Behavior
- Hooks
- Package
- React
Installation
Install the React package, load its stylesheet once, then import primitives directly. React ships the same styled markup and data contract, including data-display, overlay, form, rich text, and schedule attributes; use hooks for behavior-heavy primitives.
pnpm
pnpm add @santi020k/lumen-reactpnpm add @santi020k/lumen-reactnpm
npm install @santi020k/lumen-reactnpm install @santi020k/lumen-reactyarn
yarn add @santi020k/lumen-reactyarn add @santi020k/lumen-reactUsage Example
import { Button, Card, Input } from '@santi020k/lumen-react'
export function SubscribeForm() {
return (
<Card>
<label htmlFor="email">Email</label>
<Input id="email" type="email" placeholder="you@example.com" />
<Button>Subscribe</Button>
</Card>
)
}import { Button, Card, Input } from '@santi020k/lumen-react'
export function SubscribeForm() {
return (
<Card>
<label htmlFor="email">Email</label>
<Input id="email" type="email" placeholder="you@example.com" />
<Button>Subscribe</Button>
</Card>
)
}Overview
- React components mirror the same ui-* classes, data attributes, and prop names where React naming allows it.
- DataTable renders the same structured row contract and VirtualList emits the shared sizing attributes for app-level adapters.
- Use React hooks such as useDialog, usePopover, useDropdownMenu, useContextMenu, useTabs, useSelect, useFormValidation, useCalendar, useInputOTP, useDateRangePicker, useRichTextEditor, useSchedule, useResizable, useThemeBuilder, useToast, and useTooltip for behavior-heavy primitives.
- Use lumen add Component --target react or lumen add recipe-name --target react when you want local .tsx starter files.
Hooks Reference
React hooks provide the behavior and state management for complex primitives.
useDialog
Manages modal and alert dialog lifecycle — open, close, focus trapping, backdrop click dismiss, and Escape key handling. Spreads props onto a <dialog> element.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| alert | boolean | false | Render as an alert dialog that blocks backdrop-click dismiss. |
| defaultOpen | boolean | false | Initial open state for uncontrolled usage. |
| open | boolean | - | Controlled open state. When provided the hook does not manage open internally. |
| onOpenChange | (open: boolean) => void | - | Callback fired when the open state changes. |
| id | string | auto | Explicit id for the dialog element. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| dialogProps | LumenProps<"dialog"> | Spread onto the <dialog> element. Includes aria-modal, data attributes, click and keydown handlers. |
| triggerProps | LumenProps<"button"> | Spread onto the trigger button. Binds the click handler that opens the dialog. |
| closeProps | LumenProps<"button"> | Spread onto any close button inside the dialog. |
| dialogRef | RefObject<HTMLDialogElement> | Ref to the dialog DOM element. |
| triggerRef | RefObject<HTMLElement> | Ref to the trigger DOM element. |
| open | boolean | Current open state. |
| close | () => void | Imperatively close the dialog. |
| setOpen | Dispatch<SetStateAction<boolean>> | State setter for controlled usage. |
Example
import { Button, Dialog, Card } from '@santi020k/lumen-react'
import { useDialog } from '@santi020k/lumen-react'
export function ConfirmDialog() {
const dialog = useDialog()
return (
<>
<Button {...dialog.triggerProps}>Open dialog</Button>
<Dialog {...dialog.dialogProps}>
<Card>
<h2>Confirm action</h2>
<p>Are you sure you want to continue?</p>
<Button {...dialog.closeProps}>Close</Button>
</Card>
</Dialog>
</>
)
}import { Button, Dialog, Card } from '@santi020k/lumen-react'
import { useDialog } from '@santi020k/lumen-react'
export function ConfirmDialog() {
const dialog = useDialog()
return (
<>
<Button {...dialog.triggerProps}>Open dialog</Button>
<Dialog {...dialog.dialogProps}>
<Card>
<h2>Confirm action</h2>
<p>Are you sure you want to continue?</p>
<Button {...dialog.closeProps}>Close</Button>
</Card>
</Dialog>
</>
)
}usePopover
Manages popover disclosure — toggle, outside click close, Escape key dismiss, and arrow key navigation through focusable panel items.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| defaultOpen | boolean | false | Initial open state for uncontrolled usage. |
| open | boolean | - | Controlled open state. |
| onOpenChange | (open: boolean) => void | - | Callback fired when the open state changes. |
| id | string | auto | Explicit id for the panel element. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the root wrapper element. |
| triggerProps | LumenProps<"button"> | Spread onto the trigger button. Binds click, ArrowDown, Enter, and Space key handlers. |
| panelProps | LumenProps<"div"> | Spread onto the popover panel. Includes Escape, arrow, Home, and End key handlers. |
| rootRef | RefObject<HTMLElement> | Ref to the root wrapper. |
| triggerRef | RefObject<HTMLElement> | Ref to the trigger element. |
| panelRef | RefObject<HTMLElement> | Ref to the panel element. |
| open | boolean | Current open state. |
| close | () => void | Close the popover. |
| toggle | () => void | Toggle the popover. |
| setOpen | Dispatch<SetStateAction<boolean>> | State setter for controlled usage. |
Example
import { Button, Popover, PopoverTrigger, PopoverPanel } from '@santi020k/lumen-react'
import { usePopover } from '@santi020k/lumen-react'
export function NotificationsPopover() {
const popover = usePopover()
return (
<Popover {...popover.rootProps}>
<PopoverTrigger {...popover.triggerProps}>Notifications</PopoverTrigger>
<PopoverPanel {...popover.panelProps}>
<p>No new notifications.</p>
</PopoverPanel>
</Popover>
)
}import { Button, Popover, PopoverTrigger, PopoverPanel } from '@santi020k/lumen-react'
import { usePopover } from '@santi020k/lumen-react'
export function NotificationsPopover() {
const popover = usePopover()
return (
<Popover {...popover.rootProps}>
<PopoverTrigger {...popover.triggerProps}>Notifications</PopoverTrigger>
<PopoverPanel {...popover.panelProps}>
<p>No new notifications.</p>
</PopoverPanel>
</Popover>
)
}useDropdownMenu
Manages dropdown menu disclosure — identical API to usePopover but sets aria-haspopup to "menu" for correct ARIA semantics on menu triggers.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| defaultOpen | boolean | false | Initial open state. |
| open | boolean | - | Controlled open state. |
| onOpenChange | (open: boolean) => void | - | Callback fired when the open state changes. |
| id | string | auto | Explicit id for the menu panel. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the root wrapper. |
| triggerProps | LumenProps<"button"> | Spread onto the menu trigger. Sets aria-haspopup="menu". |
| panelProps | LumenProps<"div"> | Spread onto the menu panel with keyboard navigation. |
| open | boolean | Current open state. |
| close | () => void | Close the menu. |
| toggle | () => void | Toggle the menu. |
Example
import { Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent } from '@santi020k/lumen-react'
import { useDropdownMenu } from '@santi020k/lumen-react'
export function ActionsMenu() {
const menu = useDropdownMenu()
return (
<DropdownMenu {...menu.rootProps}>
<DropdownMenuTrigger {...menu.triggerProps}>Actions</DropdownMenuTrigger>
<DropdownMenuContent {...menu.panelProps}>
<button>Edit</button>
<button>Delete</button>
</DropdownMenuContent>
</DropdownMenu>
)
}import { Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent } from '@santi020k/lumen-react'
import { useDropdownMenu } from '@santi020k/lumen-react'
export function ActionsMenu() {
const menu = useDropdownMenu()
return (
<DropdownMenu {...menu.rootProps}>
<DropdownMenuTrigger {...menu.triggerProps}>Actions</DropdownMenuTrigger>
<DropdownMenuContent {...menu.panelProps}>
<button>Edit</button>
<button>Delete</button>
</DropdownMenuContent>
</DropdownMenu>
)
}useContextMenu
Binds a trigger to a context menu that opens on right-click or Shift+F10 and supports Escape and roving item focus.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| defaultOpen | boolean | false | Initial open state. |
| open | boolean | - | Controlled open state. |
| onOpenChange | (open: boolean) => void | - | Callback fired when the context menu opens or closes. |
| id | string | auto | Explicit id for the menu. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| triggerProps | LumenProps<"button"> | Spread onto the trigger. Handles contextmenu and Shift+F10. |
| menuProps | LumenProps<"menu"> | Spread onto ContextMenu. Includes data-ui-context-menu, role, hidden state, and keyboard handlers. |
| triggerRef | RefObject<HTMLElement> | Ref to the trigger. |
| menuRef | RefObject<HTMLElement> | Ref to the menu. |
| open | boolean | Current open state. |
| openAt | (x: number, y: number) => void | Open the menu at viewport coordinates. |
| close | () => void | Close the menu. |
Example
import { ContextMenu, useContextMenu } from '@santi020k/lumen-react'
export function ProjectActions() {
const menu = useContextMenu({ id: 'project-actions' })
return (
<>
<button {...menu.triggerProps}>Project</button>
<ContextMenu {...menu.menuProps}>
<button role="menuitem">Duplicate</button>
<button role="menuitem">Delete</button>
</ContextMenu>
</>
)
}import { ContextMenu, useContextMenu } from '@santi020k/lumen-react'
export function ProjectActions() {
const menu = useContextMenu({ id: 'project-actions' })
return (
<>
<button {...menu.triggerProps}>Project</button>
<ContextMenu {...menu.menuProps}>
<button role="menuitem">Duplicate</button>
<button role="menuitem">Delete</button>
</ContextMenu>
</>
)
}useTabs
Manages a tabbed interface with controlled/uncontrolled value, arrow key navigation, roving tabindex, and automatic ARIA attributes for tab triggers and panels.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| defaultValue | string | "" | Initial active tab value for uncontrolled usage. |
| value | string | - | Controlled active tab value. |
| onValueChange | (value: string) => void | - | Callback fired when the active tab changes. |
| orientation | "horizontal" | "vertical" | "horizontal" | Tab navigation direction — affects which arrow keys are used. |
| id | string | auto | Base id for generated tab and panel ids. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the tabs root wrapper. |
| listProps | LumenProps<"div"> | Spread onto the tablist element. |
| getTriggerProps(value) | (value, props?) => LumenProps<"button"> | Returns props for a tab trigger with the given value. Includes aria-selected, role, keyboard handlers. |
| getPanelProps(value) | (value, props?) => LumenProps<"div"> | Returns props for a tab panel. Automatically hides non-active panels. |
| value | string | Currently active tab value. |
| setValue | Dispatch<SetStateAction<string>> | State setter for controlled usage. |
| rootRef | RefObject<HTMLElement> | Ref to the tabs root element. |
Example
import { Tabs, TabsList, TabsTrigger, TabsPanel } from '@santi020k/lumen-react'
import { useTabs } from '@santi020k/lumen-react'
export function SettingsTabs() {
const tabs = useTabs({ defaultValue: 'general' })
return (
<Tabs {...tabs.rootProps}>
<TabsList {...tabs.listProps}>
<TabsTrigger {...tabs.getTriggerProps('general')}>General</TabsTrigger>
<TabsTrigger {...tabs.getTriggerProps('security')}>Security</TabsTrigger>
</TabsList>
<TabsPanel {...tabs.getPanelProps('general')}>
<p>General settings content.</p>
</TabsPanel>
<TabsPanel {...tabs.getPanelProps('security')}>
<p>Security settings content.</p>
</TabsPanel>
</Tabs>
)
}import { Tabs, TabsList, TabsTrigger, TabsPanel } from '@santi020k/lumen-react'
import { useTabs } from '@santi020k/lumen-react'
export function SettingsTabs() {
const tabs = useTabs({ defaultValue: 'general' })
return (
<Tabs {...tabs.rootProps}>
<TabsList {...tabs.listProps}>
<TabsTrigger {...tabs.getTriggerProps('general')}>General</TabsTrigger>
<TabsTrigger {...tabs.getTriggerProps('security')}>Security</TabsTrigger>
</TabsList>
<TabsPanel {...tabs.getPanelProps('general')}>
<p>General settings content.</p>
</TabsPanel>
<TabsPanel {...tabs.getPanelProps('security')}>
<p>Security settings content.</p>
</TabsPanel>
</Tabs>
)
}useSelect
Manages a fully custom select with native <select> fallback, ARIA listbox, typeahead character search, keyboard navigation, and external form integration.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| options | Array<string | SelectOption> | [] | Available options. Strings are converted to { label, value } objects. |
| defaultValue | string | "" | Initial selected value. |
| value | string | - | Controlled selected value. |
| onValueChange | (value: string) => void | - | Callback fired when the selected value changes. |
| placeholder | string | - | Placeholder text shown when no option is selected. |
| disabled | boolean | false | Disable the select trigger. |
| required | boolean | false | Mark the hidden native select as required for form validation. |
| name | string | - | Name attribute for the hidden native select. |
| id | string | auto | Explicit id for the listbox. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the select root wrapper. |
| triggerProps | LumenProps<"button"> | Spread onto the trigger button. Includes keyboard navigation. |
| listProps | LumenProps<"div"> | Spread onto the listbox container. |
| controlProps | LumenProps<"div"> | Spread onto the visual control wrapper. |
| nativeSelectProps | LumenProps<"select"> | Spread onto a hidden native <select> for form compatibility. |
| getOptionProps(option) | (option, props?) => LumenProps<"button"> | Returns props for an option button. |
| selectedOption | SelectOption | undefined | Currently selected option object. |
| triggerText | string | Display text for the trigger (selected label or placeholder). |
| value | string | Currently selected value. |
| options | SelectOption[] | Normalized options array. |
| open | boolean | Whether the listbox is open. |
| close | () => void | Close the listbox. |
| selectOption | (value: string) => void | Programmatically select an option by value. |
Example
import { Select } from '@santi020k/lumen-react'
import { useSelect } from '@santi020k/lumen-react'
export function CountrySelect() {
const select = useSelect({
options: ['United States', 'Canada', 'Mexico'],
placeholder: 'Choose a country',
onValueChange: (value) => console.log('Selected:', value)
})
return (
<div {...select.rootProps}>
<button {...select.triggerProps}>{select.triggerText}</button>
<select {...select.nativeSelectProps} />
<div {...select.listProps}>
{select.options.map(option => (
<button key={option.value} {...select.getOptionProps(option)}>
{option.label}
</button>
))}
</div>
</div>
)
}import { Select } from '@santi020k/lumen-react'
import { useSelect } from '@santi020k/lumen-react'
export function CountrySelect() {
const select = useSelect({
options: ['United States', 'Canada', 'Mexico'],
placeholder: 'Choose a country',
onValueChange: (value) => console.log('Selected:', value)
})
return (
<div {...select.rootProps}>
<button {...select.triggerProps}>{select.triggerText}</button>
<select {...select.nativeSelectProps} />
<div {...select.listProps}>
{select.options.map(option => (
<button key={option.value} {...select.getOptionProps(option)}>
{option.label}
</button>
))}
</div>
</div>
)
}useTooltip
Manages tooltip show/hide with configurable delay, hover and focus triggers, and Escape key dismissal.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| delay | number | 400 | Milliseconds to wait before showing the tooltip on hover. |
| defaultOpen | boolean | false | Initial open state. |
| open | boolean | - | Controlled open state. |
| onOpenChange | (open: boolean) => void | - | Callback fired when the tooltip opens or closes. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"span"> | Spread onto the wrapper span. Includes hover and focus listeners. |
| tooltipProps | LumenProps<"span"> | Spread onto the tooltip content span. Includes role="tooltip" and visibility. |
| open | boolean | Current open state. |
| close | () => void | Close the tooltip. |
| setOpen | Dispatch<SetStateAction<boolean>> | State setter for controlled usage. |
Example
import { Button, Tooltip, TooltipContent } from '@santi020k/lumen-react'
import { useTooltip } from '@santi020k/lumen-react'
export function SaveButton() {
const tooltip = useTooltip({ delay: 300 })
return (
<Tooltip {...tooltip.rootProps}>
<Button>Save</Button>
<TooltipContent {...tooltip.tooltipProps}>Save your changes</TooltipContent>
</Tooltip>
)
}import { Button, Tooltip, TooltipContent } from '@santi020k/lumen-react'
import { useTooltip } from '@santi020k/lumen-react'
export function SaveButton() {
const tooltip = useTooltip({ delay: 300 })
return (
<Tooltip {...tooltip.rootProps}>
<Button>Save</Button>
<TooltipContent {...tooltip.tooltipProps}>Save your changes</TooltipContent>
</Tooltip>
)
}useToast
Consumes the ToastProvider context to create, update, and dismiss toast notifications. Requires a ToastProvider ancestor in the component tree.
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| create | (detail: ToastDetail) => string | Create a toast and return its id. Accepts title, description, variant, duration, placement, and an optional action. |
| dismiss | (id?: string) => void | Dismiss a specific toast by id, or dismiss the most recent toast if no id is provided. |
| update | (id: string, detail: ToastDetail) => void | Update an existing toast with new content. |
| toasts | ToastRecord[] | Array of all active toast records. |
Example
import { Button, ToastProvider } from '@santi020k/lumen-react'
import { useToast } from '@santi020k/lumen-react'
function SaveButton() {
const toast = useToast()
return (
<Button onClick={() => toast.create({
title: 'Saved',
description: 'Your changes are live.',
variant: 'success'
})}>
Save
</Button>
)
}
// Wrap your app with ToastProvider
export function App() {
return (
<ToastProvider placement="bottom-right" maxCount={5}>
<SaveButton />
</ToastProvider>
)
}import { Button, ToastProvider } from '@santi020k/lumen-react'
import { useToast } from '@santi020k/lumen-react'
function SaveButton() {
const toast = useToast()
return (
<Button onClick={() => toast.create({
title: 'Saved',
description: 'Your changes are live.',
variant: 'success'
})}>
Save
</Button>
)
}
// Wrap your app with ToastProvider
export function App() {
return (
<ToastProvider placement="bottom-right" maxCount={5}>
<SaveButton />
</ToastProvider>
)
}useFormValidation
Enhances native HTML form validation with custom error messages via data-error-* attributes, live field description syncing, and programmatic validity control.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| onValidate | (detail: FormValidationValidateDetail) => void | - | Callback fired when a single control is validated. |
| onValid | (detail: FormValidationStateDetail) => void | - | Callback fired when the form becomes valid. |
| onInvalid | (detail: FormValidationStateDetail) => void | - | Callback fired when the form has invalid controls. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| formProps | LumenProps<"form"> | Spread onto the <form> element. Adds native validation bypass and submit handler. |
| formRef | RefObject<HTMLFormElement> | Ref to the form element. |
| getControls | (form?) => NativeFormControl[] | Returns all form controls, including inputs, selects, and text areas. |
| setFieldValidity | (control, invalid, message?) => void | Programmatically set a field as valid or invalid with a custom message. |
| validateControl | (control, form?) => boolean | Validate a single control and return whether it is valid. |
| validateForm | (form?) => NativeFormControl[] | Validate all controls and return the array of invalid controls. |
Example
import { Button, Field, Input, Label } from '@santi020k/lumen-react'
import { useFormValidation } from '@santi020k/lumen-react'
export function SignupForm() {
const form = useFormValidation({
onValid: () => console.log('Form is valid'),
onInvalid: ({ controls }) =>
console.log('Invalid fields:', controls?.length)
})
return (
<form {...form.formProps}>
<Field>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
required
data-error-required="Please enter your email"
data-error-type="Enter a valid email address"
/>
</Field>
<Button type="submit">Sign up</Button>
</form>
)
}import { Button, Field, Input, Label } from '@santi020k/lumen-react'
import { useFormValidation } from '@santi020k/lumen-react'
export function SignupForm() {
const form = useFormValidation({
onValid: () => console.log('Form is valid'),
onInvalid: ({ controls }) =>
console.log('Invalid fields:', controls?.length)
})
return (
<form {...form.formProps}>
<Field>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
required
data-error-required="Please enter your email"
data-error-type="Enter a valid email address"
/>
</Field>
<Button type="submit">Sign up</Button>
</form>
)
}useCalendar
Builds an Astro-compatible calendar grid with hidden input value, min/max clamping, month navigation, selection, and roving keyboard focus.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| value | string | - | Controlled selected date in YYYY-MM-DD format. |
| defaultValue | string | "" | Initial selected date for uncontrolled usage. |
| month | string | selected month or today | Visible month in YYYY-MM format. |
| min / max | string | - | Inclusive date bounds in YYYY-MM-DD format. |
| onValueChange | (value: string) => void | - | Callback fired when a date is selected. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the calendar root. |
| inputProps | LumenProps<"input"> | Spread onto the hidden input. |
| previousProps / nextProps | LumenProps<"button"> | Spread onto month navigation buttons. |
| weeks | CalendarDay[][] | Six calendar rows of generated day models. |
| getDayProps | (day, props?) => LumenProps<"td"> | Returns gridcell props for a generated day. |
| selectDate | (date) => void | Select a date programmatically. |
Example
import { Calendar } from '@santi020k/lumen-react'
export function DeliveryDate() {
return (
<Calendar
max="2026-07-31"
min="2026-07-01"
month="2026-07"
name="delivery"
/>
)
}import { Calendar } from '@santi020k/lumen-react'
export function DeliveryDate() {
return (
<Calendar
max="2026-07-31"
min="2026-07-01"
month="2026-07"
name="delivery"
/>
)
}useDateRangePicker
Keeps paired native date inputs constrained so the end date cannot be before the start date.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| onRangeChange | (detail: DateRangePickerChangeDetail) => void | - | Callback fired after the date range is synchronized. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the date range root. |
| rootRef | RefObject<HTMLElement> | Ref to the date range root. |
| getStartProps | (props?) => LumenProps<"input"> | Returns props for the start date input. |
| getEndProps | (props?) => LumenProps<"input"> | Returns props for the end date input. |
| syncRange | (root?) => DateRangePickerChangeDetail | Synchronize min/max constraints and return the current range. |
Example
import { useDateRangePicker } from '@santi020k/lumen-react'
export function BookingRange() {
const range = useDateRangePicker()
return (
<div className="ui-date-range-picker" {...range.rootProps}>
<input className="ui-input ui-date-picker" aria-label="Start date" {...range.getStartProps()} />
<input className="ui-input ui-date-picker" aria-label="End date" {...range.getEndProps()} />
</div>
)
}import { useDateRangePicker } from '@santi020k/lumen-react'
export function BookingRange() {
const range = useDateRangePicker()
return (
<div className="ui-date-range-picker" {...range.rootProps}>
<input className="ui-input ui-date-picker" aria-label="Start date" {...range.getStartProps()} />
<input className="ui-input ui-date-picker" aria-label="End date" {...range.getEndProps()} />
</div>
)
}useInputOTP
Builds an Astro-compatible OTP field with a native input, visual segments, sanitized values, and caret navigation.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| length | number | 6 | Number of visible OTP segments. |
| value | string | - | Controlled OTP value. |
| defaultValue | string | "" | Initial value for uncontrolled usage. |
| onValueChange | (value: string) => void | - | Callback fired when input or paste changes the sanitized value. |
| inputMode | string | "numeric" | Native inputMode forwarded to the hidden input. Numeric mode removes non-digits. |
| pattern | string | "[0-9]*" | Native pattern forwarded to the hidden input. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the OTP field root. |
| getInputProps | (props?) => LumenProps<"input"> | Returns props for the native input. |
| segmentsProps | LumenProps<"div"> | Spread onto the visual segment container. |
| getSegmentProps | (index, props?) => LumenProps<"button"> | Returns props for a visual segment button. |
| getSegmentChar | (index) => string | Returns the character or blank placeholder for a segment. |
| setValue | (value) => string | Sanitize and set the current OTP value. |
Example
import { useInputOTP } from '@santi020k/lumen-react'
export function VerificationCode() {
const otp = useInputOTP({ length: 6, defaultValue: '123456' })
return (
<div {...otp.rootProps}>
<input aria-label="Verification code" name="code" {...otp.getInputProps()} />
<div {...otp.segmentsProps}>
{otp.segmentIndexes.map(index => (
<button key={index} {...otp.getSegmentProps(index)}>
<span data-ui-input-otp-char>{otp.getSegmentChar(index)}</span>
</button>
))}
</div>
</div>
)
}import { useInputOTP } from '@santi020k/lumen-react'
export function VerificationCode() {
const otp = useInputOTP({ length: 6, defaultValue: '123456' })
return (
<div {...otp.rootProps}>
<input aria-label="Verification code" name="code" {...otp.getInputProps()} />
<div {...otp.segmentsProps}>
{otp.segmentIndexes.map(index => (
<button key={index} {...otp.getSegmentProps(index)}>
<span data-ui-input-otp-char>{otp.getSegmentChar(index)}</span>
</button>
))}
</div>
</div>
)
}useRichTextEditor
Provides rich text editing via document.execCommand. Returns props for the editable root and factory for toolbar command buttons.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| onCommand | (detail: { command: string }) => void | - | Callback fired when a command is executed. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"section"> | Spread onto the editor root section. |
| rootRef | RefObject<HTMLElement> | Ref to the editor root. |
| executeCommand | (command: string, root?) => boolean | Execute a rich text command (bold, italic, insertUnorderedList, etc.). |
| getCommandProps | (command, props?) => LumenProps<"button"> | Returns props for a toolbar button bound to a specific command. |
Example
import { Button, RichTextEditor } from '@santi020k/lumen-react'
import { useRichTextEditor } from '@santi020k/lumen-react'
export function Editor() {
const editor = useRichTextEditor()
return (
<RichTextEditor {...editor.rootProps}>
<div role="toolbar" aria-label="Formatting">
<Button {...editor.getCommandProps('bold')} size="icon">B</Button>
<Button {...editor.getCommandProps('italic')} size="icon">I</Button>
<Button {...editor.getCommandProps('insertUnorderedList')} size="icon">•</Button>
</div>
<div contentEditable data-ui-rich-text-editable>
<p>Start writing...</p>
</div>
</RichTextEditor>
)
}import { Button, RichTextEditor } from '@santi020k/lumen-react'
import { useRichTextEditor } from '@santi020k/lumen-react'
export function Editor() {
const editor = useRichTextEditor()
return (
<RichTextEditor {...editor.rootProps}>
<div role="toolbar" aria-label="Formatting">
<Button {...editor.getCommandProps('bold')} size="icon">B</Button>
<Button {...editor.getCommandProps('italic')} size="icon">I</Button>
<Button {...editor.getCommandProps('insertUnorderedList')} size="icon">•</Button>
</div>
<div contentEditable data-ui-rich-text-editable>
<p>Start writing...</p>
</div>
</RichTextEditor>
)
}useSchedule
Provides props for schedule/agenda slot and event elements, and emits change details when a slot or event is interacted with.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| onChange | (detail: ScheduleChangeDetail) => void | - | Callback fired when a schedule slot or event changes. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"section"> | Spread onto the schedule root. |
| rootRef | RefObject<HTMLElement> | Ref to the schedule root. |
| getSlotProps | (slot, props?) => LumenProps<"section"> | Returns props for a time slot element. |
| getEventProps | (eventId?, props?) => LumenProps<"article"> | Returns props for an event element. |
| emitChange | (detail, root?) => void | Programmatically emit a schedule change detail. |
Example
import { Schedule, Card } from '@santi020k/lumen-react'
import { useSchedule } from '@santi020k/lumen-react'
export function WeekView() {
const schedule = useSchedule({
onChange: ({ eventId, slot }) =>
console.log('Changed:', eventId, slot)
})
return (
<Schedule {...schedule.rootProps}>
<section {...schedule.getSlotProps('2025-01-06T09:00')}>
<Card {...schedule.getEventProps('meeting-1')}>
<strong>Team standup</strong>
<p>9:00 – 9:30</p>
</Card>
</section>
</Schedule>
)
}import { Schedule, Card } from '@santi020k/lumen-react'
import { useSchedule } from '@santi020k/lumen-react'
export function WeekView() {
const schedule = useSchedule({
onChange: ({ eventId, slot }) =>
console.log('Changed:', eventId, slot)
})
return (
<Schedule {...schedule.rootProps}>
<section {...schedule.getSlotProps('2025-01-06T09:00')}>
<Card {...schedule.getEventProps('meeting-1')}>
<strong>Team standup</strong>
<p>9:00 – 9:30</p>
</Card>
</section>
</Schedule>
)
}useResizable
Provides pane sizing props and separator handle props for horizontal or vertical resizable groups.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| panelCount | number | 2 | Number of panes in the group. |
| defaultSizes | number[] | equal panes | Initial pane sizes normalized to 100%. |
| minSize | number | number[] | 12 | Minimum pane size percentage. |
| maxSize | number | number[] | 88 | Maximum pane size percentage. |
| direction | "horizontal" | "vertical" | "horizontal" | Resize axis and generated separator orientation. |
| resetOnDoubleClick | boolean | true | Reset panes to their default sizes on handle double-click. |
| onSizesChange | (sizes: number[]) => void | - | Callback fired when panes are resized. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"div"> | Spread onto the resizable root. |
| getPanelProps | (index, props?) => LumenProps<"div"> | Returns data/style props for a pane. |
| getHandleProps | (index, props?) => LumenProps<"button"> | Returns separator handle props with pointer and keyboard handlers. |
| sizes | number[] | Current pane sizes as percentages. |
| resizePair | (index, nextSize) => void | Resize one pane and its following pane while honoring min/max. |
| reset | () => void | Reset to the normalized default sizes. |
Example
import { Resizable } from '@santi020k/lumen-react'
export function SplitEditor() {
return (
<Resizable defaultSizes={[30, 70]}>
<aside>Navigation</aside>
<main>Editor</main>
</Resizable>
)
}import { Resizable } from '@santi020k/lumen-react'
export function SplitEditor() {
return (
<Resizable defaultSizes={[30, 70]}>
<aside>Navigation</aside>
<main>Editor</main>
</Resizable>
)
}useThemeBuilder
Interactive theme builder with controllable hue, accent, mode (generated/manual), scheme (light/dark), color pickers, and export in CSS, Figma variables, or design token formats.
Options
| Property | Type | Default | Description |
|---|---|---|---|
| defaultHue | number | 210 | Initial hue value. |
| hue | number | - | Controlled hue. |
| onHueChange | (hue: number) => void | - | Callback when the hue changes. |
| defaultAccentHue | number | - | Initial accent hue. |
| accentHue | number | - | Controlled accent hue. |
| onAccentHueChange | (hue: number) => void | - | Callback when the accent hue changes. |
| defaultScheme | "light" | "dark" | "dark" | Initial color scheme. |
| scheme | "light" | "dark" | - | Controlled color scheme. |
| onSchemeChange | (scheme) => void | - | Callback when the scheme changes. |
| defaultMode | "generated" | "manual" | "generated" | Initial builder mode. |
| mode | "generated" | "manual" | - | Controlled builder mode. |
| defaultExportFormat | "css" | "figma" | "tokens" | "css" | Initial export format. |
| onThemeChange | (detail: ThemeBuilderChangeDetail) => void | - | Callback when any theme value changes. |
| onThemeExport | (detail: ThemeBuilderExportDetail) => void | - | Callback when the user exports the theme. |
Controller (Returns)
| Property | Type | Description |
|---|---|---|
| rootProps | LumenProps<"section"> | Spread onto the theme builder root. |
| hueProps | LumenProps<"input"> | Spread onto the hue range input. |
| accentHueProps | LumenProps<"input"> | Spread onto the accent hue range input. |
| primaryColorProps | LumenProps<"input"> | Spread onto the primary color picker input. |
| secondaryColorProps | LumenProps<"input"> | Spread onto the secondary color picker input. |
| outputProps | LumenProps<"textarea"> | Spread onto the export output textarea. |
| exportButtonProps | LumenProps<"button"> | Spread onto the copy/export button. |
| getModeProps | (mode, props?) => LumenProps<"button"> | Returns toggle props for a mode button. |
| getSchemeProps | (scheme, props?) => LumenProps<"button"> | Returns toggle props for a scheme button. |
| getExportFormatProps | (format, props?) => LumenProps<"button"> | Returns toggle props for an export format button. |
| previewStyle | CSSProperties | Inline style object with generated CSS custom properties for live preview. |
| exportValue | string | Current export string (CSS, Figma JSON, or design tokens JSON). |
| exportFormat | "css" | "figma" | "tokens" | Current export format. |
| tokens | LumenThemeTokens | Current generated theme tokens. |
| copyExport | () => Promise<string> | Copy the export value to clipboard and return it. |
Example
import { ThemeBuilder, Card, Button, Slider } from '@santi020k/lumen-react'
import { useThemeBuilder } from '@santi020k/lumen-react'
export function CustomThemeBuilder() {
const builder = useThemeBuilder({
defaultHue: 264,
defaultScheme: 'dark',
onThemeExport: ({ value, format }) =>
console.log(`Exported ${format}:`, value)
})
return (
<ThemeBuilder {...builder.rootProps}>
<div style={builder.previewStyle}>
<Card glass>
<label>Brand hue</label>
<input type="range" {...builder.hueProps} min={0} max={359} />
<label>Accent hue</label>
<input type="range" {...builder.accentHueProps} min={0} max={359} />
<div>
<Button {...builder.getSchemeProps('light')}>Light</Button>
<Button {...builder.getSchemeProps('dark')}>Dark</Button>
</div>
<textarea {...builder.outputProps} readOnly rows={6} />
<Button {...builder.exportButtonProps}>Copy CSS</Button>
</Card>
</div>
</ThemeBuilder>
)
}import { ThemeBuilder, Card, Button, Slider } from '@santi020k/lumen-react'
import { useThemeBuilder } from '@santi020k/lumen-react'
export function CustomThemeBuilder() {
const builder = useThemeBuilder({
defaultHue: 264,
defaultScheme: 'dark',
onThemeExport: ({ value, format }) =>
console.log(`Exported ${format}:`, value)
})
return (
<ThemeBuilder {...builder.rootProps}>
<div style={builder.previewStyle}>
<Card glass>
<label>Brand hue</label>
<input type="range" {...builder.hueProps} min={0} max={359} />
<label>Accent hue</label>
<input type="range" {...builder.accentHueProps} min={0} max={359} />
<div>
<Button {...builder.getSchemeProps('light')}>Light</Button>
<Button {...builder.getSchemeProps('dark')}>Dark</Button>
</div>
<textarea {...builder.outputProps} readOnly rows={6} />
<Button {...builder.exportButtonProps}>Copy CSS</Button>
</Card>
</div>
</ThemeBuilder>
)
}