121
GitHub
Framework guide

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

bash
pnpm add @santi020k/lumen-react
pnpm add @santi020k/lumen-react

npm

bash
npm install @santi020k/lumen-react
npm install @santi020k/lumen-react

yarn

bash
yarn add @santi020k/lumen-react
yarn add @santi020k/lumen-react

Usage Example

tsx
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

PropertyTypeDefaultDescription
alertbooleanfalseRender as an alert dialog that blocks backdrop-click dismiss.
defaultOpenbooleanfalseInitial open state for uncontrolled usage.
openboolean-Controlled open state. When provided the hook does not manage open internally.
onOpenChange(open: boolean) => void-Callback fired when the open state changes.
idstringautoExplicit id for the dialog element.

Controller (Returns)

PropertyTypeDescription
dialogPropsLumenProps<"dialog">Spread onto the <dialog> element. Includes aria-modal, data attributes, click and keydown handlers.
triggerPropsLumenProps<"button">Spread onto the trigger button. Binds the click handler that opens the dialog.
closePropsLumenProps<"button">Spread onto any close button inside the dialog.
dialogRefRefObject<HTMLDialogElement>Ref to the dialog DOM element.
triggerRefRefObject<HTMLElement>Ref to the trigger DOM element.
openbooleanCurrent open state.
close() => voidImperatively close the dialog.
setOpenDispatch<SetStateAction<boolean>>State setter for controlled usage.

Example

tsx
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

PropertyTypeDefaultDescription
defaultOpenbooleanfalseInitial open state for uncontrolled usage.
openboolean-Controlled open state.
onOpenChange(open: boolean) => void-Callback fired when the open state changes.
idstringautoExplicit id for the panel element.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the root wrapper element.
triggerPropsLumenProps<"button">Spread onto the trigger button. Binds click, ArrowDown, Enter, and Space key handlers.
panelPropsLumenProps<"div">Spread onto the popover panel. Includes Escape, arrow, Home, and End key handlers.
rootRefRefObject<HTMLElement>Ref to the root wrapper.
triggerRefRefObject<HTMLElement>Ref to the trigger element.
panelRefRefObject<HTMLElement>Ref to the panel element.
openbooleanCurrent open state.
close() => voidClose the popover.
toggle() => voidToggle the popover.
setOpenDispatch<SetStateAction<boolean>>State setter for controlled usage.

Example

tsx
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

PropertyTypeDefaultDescription
defaultOpenbooleanfalseInitial open state.
openboolean-Controlled open state.
onOpenChange(open: boolean) => void-Callback fired when the open state changes.
idstringautoExplicit id for the menu panel.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the root wrapper.
triggerPropsLumenProps<"button">Spread onto the menu trigger. Sets aria-haspopup="menu".
panelPropsLumenProps<"div">Spread onto the menu panel with keyboard navigation.
openbooleanCurrent open state.
close() => voidClose the menu.
toggle() => voidToggle the menu.

Example

tsx
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

PropertyTypeDefaultDescription
defaultOpenbooleanfalseInitial open state.
openboolean-Controlled open state.
onOpenChange(open: boolean) => void-Callback fired when the context menu opens or closes.
idstringautoExplicit id for the menu.

Controller (Returns)

PropertyTypeDescription
triggerPropsLumenProps<"button">Spread onto the trigger. Handles contextmenu and Shift+F10.
menuPropsLumenProps<"menu">Spread onto ContextMenu. Includes data-ui-context-menu, role, hidden state, and keyboard handlers.
triggerRefRefObject<HTMLElement>Ref to the trigger.
menuRefRefObject<HTMLElement>Ref to the menu.
openbooleanCurrent open state.
openAt(x: number, y: number) => voidOpen the menu at viewport coordinates.
close() => voidClose the menu.

Example

tsx
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

PropertyTypeDefaultDescription
defaultValuestring""Initial active tab value for uncontrolled usage.
valuestring-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.
idstringautoBase id for generated tab and panel ids.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the tabs root wrapper.
listPropsLumenProps<"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.
valuestringCurrently active tab value.
setValueDispatch<SetStateAction<string>>State setter for controlled usage.
rootRefRefObject<HTMLElement>Ref to the tabs root element.

Example

tsx
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

PropertyTypeDefaultDescription
optionsArray<string | SelectOption>[]Available options. Strings are converted to { label, value } objects.
defaultValuestring""Initial selected value.
valuestring-Controlled selected value.
onValueChange(value: string) => void-Callback fired when the selected value changes.
placeholderstring-Placeholder text shown when no option is selected.
disabledbooleanfalseDisable the select trigger.
requiredbooleanfalseMark the hidden native select as required for form validation.
namestring-Name attribute for the hidden native select.
idstringautoExplicit id for the listbox.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the select root wrapper.
triggerPropsLumenProps<"button">Spread onto the trigger button. Includes keyboard navigation.
listPropsLumenProps<"div">Spread onto the listbox container.
controlPropsLumenProps<"div">Spread onto the visual control wrapper.
nativeSelectPropsLumenProps<"select">Spread onto a hidden native <select> for form compatibility.
getOptionProps(option)(option, props?) => LumenProps<"button">Returns props for an option button.
selectedOptionSelectOption | undefinedCurrently selected option object.
triggerTextstringDisplay text for the trigger (selected label or placeholder).
valuestringCurrently selected value.
optionsSelectOption[]Normalized options array.
openbooleanWhether the listbox is open.
close() => voidClose the listbox.
selectOption(value: string) => voidProgrammatically select an option by value.

Example

tsx
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

PropertyTypeDefaultDescription
delaynumber400Milliseconds to wait before showing the tooltip on hover.
defaultOpenbooleanfalseInitial open state.
openboolean-Controlled open state.
onOpenChange(open: boolean) => void-Callback fired when the tooltip opens or closes.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"span">Spread onto the wrapper span. Includes hover and focus listeners.
tooltipPropsLumenProps<"span">Spread onto the tooltip content span. Includes role="tooltip" and visibility.
openbooleanCurrent open state.
close() => voidClose the tooltip.
setOpenDispatch<SetStateAction<boolean>>State setter for controlled usage.

Example

tsx
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)

PropertyTypeDescription
create(detail: ToastDetail) => stringCreate a toast and return its id. Accepts title, description, variant, duration, placement, and an optional action.
dismiss(id?: string) => voidDismiss a specific toast by id, or dismiss the most recent toast if no id is provided.
update(id: string, detail: ToastDetail) => voidUpdate an existing toast with new content.
toastsToastRecord[]Array of all active toast records.

Example

tsx
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

PropertyTypeDefaultDescription
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)

PropertyTypeDescription
formPropsLumenProps<"form">Spread onto the <form> element. Adds native validation bypass and submit handler.
formRefRefObject<HTMLFormElement>Ref to the form element.
getControls(form?) => NativeFormControl[]Returns all form controls, including inputs, selects, and text areas.
setFieldValidity(control, invalid, message?) => voidProgrammatically set a field as valid or invalid with a custom message.
validateControl(control, form?) => booleanValidate a single control and return whether it is valid.
validateForm(form?) => NativeFormControl[]Validate all controls and return the array of invalid controls.

Example

tsx
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

PropertyTypeDefaultDescription
valuestring-Controlled selected date in YYYY-MM-DD format.
defaultValuestring""Initial selected date for uncontrolled usage.
monthstringselected month or todayVisible month in YYYY-MM format.
min / maxstring-Inclusive date bounds in YYYY-MM-DD format.
onValueChange(value: string) => void-Callback fired when a date is selected.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the calendar root.
inputPropsLumenProps<"input">Spread onto the hidden input.
previousProps / nextPropsLumenProps<"button">Spread onto month navigation buttons.
weeksCalendarDay[][]Six calendar rows of generated day models.
getDayProps(day, props?) => LumenProps<"td">Returns gridcell props for a generated day.
selectDate(date) => voidSelect a date programmatically.

Example

tsx
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

PropertyTypeDefaultDescription
onRangeChange(detail: DateRangePickerChangeDetail) => void-Callback fired after the date range is synchronized.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the date range root.
rootRefRefObject<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?) => DateRangePickerChangeDetailSynchronize min/max constraints and return the current range.

Example

tsx
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

PropertyTypeDefaultDescription
lengthnumber6Number of visible OTP segments.
valuestring-Controlled OTP value.
defaultValuestring""Initial value for uncontrolled usage.
onValueChange(value: string) => void-Callback fired when input or paste changes the sanitized value.
inputModestring"numeric"Native inputMode forwarded to the hidden input. Numeric mode removes non-digits.
patternstring"[0-9]*"Native pattern forwarded to the hidden input.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"div">Spread onto the OTP field root.
getInputProps(props?) => LumenProps<"input">Returns props for the native input.
segmentsPropsLumenProps<"div">Spread onto the visual segment container.
getSegmentProps(index, props?) => LumenProps<"button">Returns props for a visual segment button.
getSegmentChar(index) => stringReturns the character or blank placeholder for a segment.
setValue(value) => stringSanitize and set the current OTP value.

Example

tsx
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

PropertyTypeDefaultDescription
onCommand(detail: { command: string }) => void-Callback fired when a command is executed.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"section">Spread onto the editor root section.
rootRefRefObject<HTMLElement>Ref to the editor root.
executeCommand(command: string, root?) => booleanExecute 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

tsx
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

PropertyTypeDefaultDescription
onChange(detail: ScheduleChangeDetail) => void-Callback fired when a schedule slot or event changes.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"section">Spread onto the schedule root.
rootRefRefObject<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?) => voidProgrammatically emit a schedule change detail.

Example

tsx
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

PropertyTypeDefaultDescription
panelCountnumber2Number of panes in the group.
defaultSizesnumber[]equal panesInitial pane sizes normalized to 100%.
minSizenumber | number[]12Minimum pane size percentage.
maxSizenumber | number[]88Maximum pane size percentage.
direction"horizontal" | "vertical""horizontal"Resize axis and generated separator orientation.
resetOnDoubleClickbooleantrueReset panes to their default sizes on handle double-click.
onSizesChange(sizes: number[]) => void-Callback fired when panes are resized.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"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.
sizesnumber[]Current pane sizes as percentages.
resizePair(index, nextSize) => voidResize one pane and its following pane while honoring min/max.
reset() => voidReset to the normalized default sizes.

Example

tsx
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

PropertyTypeDefaultDescription
defaultHuenumber210Initial hue value.
huenumber-Controlled hue.
onHueChange(hue: number) => void-Callback when the hue changes.
defaultAccentHuenumber-Initial accent hue.
accentHuenumber-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)

PropertyTypeDescription
rootPropsLumenProps<"section">Spread onto the theme builder root.
huePropsLumenProps<"input">Spread onto the hue range input.
accentHuePropsLumenProps<"input">Spread onto the accent hue range input.
primaryColorPropsLumenProps<"input">Spread onto the primary color picker input.
secondaryColorPropsLumenProps<"input">Spread onto the secondary color picker input.
outputPropsLumenProps<"textarea">Spread onto the export output textarea.
exportButtonPropsLumenProps<"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.
previewStyleCSSPropertiesInline style object with generated CSS custom properties for live preview.
exportValuestringCurrent export string (CSS, Figma JSON, or design tokens JSON).
exportFormat"css" | "figma" | "tokens"Current export format.
tokensLumenThemeTokensCurrent generated theme tokens.
copyExport() => Promise<string>Copy the export value to clipboard and return it.

Example

tsx
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>
  )
}