Skip to main content
161

Search Lumen

Find components, APIs, guides, and recipes.

GitHub
Web docs
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, useKanban, useResizable, useThemeBuilder, useThemeToggle, 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 {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger
} from '@santi020k/lumen-react'

export function ActionsMenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger>Actions</DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Edit</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem status="Admin">Delete</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger
} from '@santi020k/lumen-react'

export function ActionsMenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger>Actions</DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Edit</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem status="Admin">Delete</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

useLanguageToggle

Coordinates controlled or uncontrolled locale cycling, persistence, and document language synchronization for React applications.

Options

PropertyTypeDefaultDescription
defaultValuestringfirst localeInitial uncontrolled locale value.
valuestring-Controlled locale value.
localesLumenLocaleOption[]English and SpanishOrdered locale values and visible labels.
storageKeystring-Persists and restores the uncontrolled locale when storage is available.
onValueChange(value: string) => void-Receives the next requested or applied locale.

Controller (Returns)

PropertyTypeDescription
valuestringCurrent locale value.
currentLocaleLumenLocaleOptionCurrent locale value and label.
nextLocaleLumenLocaleOptionNext locale value and label.
selectNext() => voidRequests or applies the next locale.

Example

tsx
import { LanguageToggle } from '@santi020k/lumen-react'

export function LocaleControl() {
  return (
    <LanguageToggle
      defaultValue="en"
      storageKey="site-language"
      locales={[
        { label: 'English', value: 'en' },
        { label: 'Español', value: 'es' }
      ]}
    />
  )
}
import { LanguageToggle } from '@santi020k/lumen-react'

export function LocaleControl() {
  return (
    <LanguageToggle
      defaultValue="en"
      storageKey="site-language"
      locales={[
        { label: 'English', value: 'en' },
        { label: 'Español', value: 'es' }
      ]}
    />
  )
}

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 commands, values, shortcuts, active toolbar state, and HTML/text change details.

Options

PropertyTypeDefaultDescription
onChange(detail: { html: string, text: string }) => void-Callback fired when editable content changes.
onCommand(detail: { command, value?, executed }) => 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?, value?) => booleanExecutes a rich text command with an optional value.
getCommandProps(command, props?) => LumenProps<"button">Returns props for a toolbar button bound to a command.
getEditableProps(props?) => LumenProps<"div">Returns accessible editable props with shortcuts and change handling.

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 {...editor.getEditableProps()}>
        <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 {...editor.getEditableProps()}>
        <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 as="article" {...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 as="article" {...schedule.getEventProps('meeting-1')}>
          <strong>Team standup</strong>
          <p>9:00 – 9:30</p>
        </Card>
      </section>
    </Schedule>
  )
}

useKanban

Provides controlled Kanban move requests and accessible props for boards, columns, items, and dedicated drag handles across keyboard, mouse, and touch input.

Options

PropertyTypeDefaultDescription
onMoveRequest(detail: LumenKanbanMoveDetail) => void-Receives an accepted move request; the application remains responsible for updating and persisting item ownership.

Controller (Returns)

PropertyTypeDescription
rootPropsLumenProps<"section">Spread onto the Kanban board root.
getColumnProps(column, props?) => LumenProps<"section">Returns drop-target props for a named column.
getItemProps(itemId, props?) => LumenProps<"article">Returns identity props for a controlled item.
getHandleProps(itemId, props?) => LumenProps<"button">Returns keyboard, pointer, and drag props for an item move handle.
requestMove(detail: LumenKanbanMoveDetail) => booleanDispatches a cancelable ui:kanban-move-request event and reports whether it was accepted.
rootRefRefObject<HTMLElement | null>Ref for the board element used to resolve items and destinations.

Example

tsx
import { Button, Card, KanbanBoard, KanbanColumn, useKanban } from '@santi020k/lumen-react'

export function EditorialBoard() {
  const kanban = useKanban({
    onMoveRequest: ({ itemId, toColumn }) =>
      console.log(`Move ${itemId} to ${toColumn}`)
  })

  return (
    <KanbanBoard {...kanban.rootProps} aria-label="Editorial workflow">
      <KanbanColumn {...kanban.getColumnProps('planned')}>
        <Card as="article" {...kanban.getItemProps('draft')}>
          <Button {...kanban.getHandleProps('draft')} aria-label="Move Draft article">
            Move
          </Button>
          <strong>Draft article</strong>
        </Card>
      </KanbanColumn>
      <KanbanColumn {...kanban.getColumnProps('published')} />
    </KanbanBoard>
  )
}
import { Button, Card, KanbanBoard, KanbanColumn, useKanban } from '@santi020k/lumen-react'

export function EditorialBoard() {
  const kanban = useKanban({
    onMoveRequest: ({ itemId, toColumn }) =>
      console.log(`Move ${itemId} to ${toColumn}`)
  })

  return (
    <KanbanBoard {...kanban.rootProps} aria-label="Editorial workflow">
      <KanbanColumn {...kanban.getColumnProps('planned')}>
        <Card as="article" {...kanban.getItemProps('draft')}>
          <Button {...kanban.getHandleProps('draft')} aria-label="Move Draft article">
            Move
          </Button>
          <strong>Draft article</strong>
        </Card>
      </KanbanColumn>
      <KanbanColumn {...kanban.getColumnProps('published')} />
    </KanbanBoard>
  )
}

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

useThemeToggle

Synchronizes a light or dark theme with the document, local storage, and the shared theme-change event, with an optional pointer-origin transition that respects reduced motion.

Options

PropertyTypeDefaultDescription
defaultThemestring"light"Theme used when browser state is unavailable or has no saved value.

Controller (Returns)

PropertyTypeDescription
themestringCurrent theme value.
setThemeDispatch<SetStateAction<string>>Sets the current theme directly.
toggleTheme(event?: MouseEvent<HTMLButtonElement>) => voidSwitches between light and dark and optionally starts the transition from the invoking button.

Example

tsx
import { ThemeToggle, useThemeToggle } from '@santi020k/lumen-react'

export function ThemeControl() {
  const { theme, toggleTheme } = useThemeToggle()

  return (
    <ThemeToggle
      aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
      onClick={toggleTheme}
    >
      {theme === 'dark' ? 'Light' : 'Dark'}
    </ThemeToggle>
  )
}
import { ThemeToggle, useThemeToggle } from '@santi020k/lumen-react'

export function ThemeControl() {
  const { theme, toggleTheme } = useThemeToggle()

  return (
    <ThemeToggle
      aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
      onClick={toggleTheme}
    >
      {theme === 'dark' ? 'Light' : 'Dark'}
    </ThemeToggle>
  )
}
Web docsFull catalog