# Lathe — guide for coding agents This project uses Lathe (https://latheui.dev), a React component library installed as source with the shadcn CLI. Stack: React 19, Tailwind CSS v4, TypeScript, Motion, Radix. ## Finding components - Index of every component with one-line descriptions: https://latheui.dev/llms.txt - Props, states, keyboard and accessibility for every component in one file: https://latheui.dev/llms-full.txt - One component as Markdown, before you use it: https://latheui.dev/md/ - Registry manifest (free items): https://latheui.dev/r/registry.json ## Installing - Every item: `npx shadcn@latest add @latheui/`. The project needs the registry once: `npx latheui init`, or `npx latheui init --key ` with Lathe Pro (see https://latheui.dev/docs/pro). - Free items also install with no setup from their full address: `npx shadcn@latest add https://latheui.dev/r/.json` - Components land in `components/ui/`, assemblies in `components/`, shared files in `lib/` and `hooks/`. - Before writing a new component, check whether Lathe already has one. Install it instead of hand-rolling. ## Prefer assemblies over raw parts When a page matches an assembly, start from the assembly and replace its sample data, rather than composing cards and inputs by hand: - Activity Replay (`activity-replay`, Pro): Inspect recorded application snapshots, play through ordered events, pin a comparison baseline, and jump to any checkpoint. - Command Preview (`command-preview`, Pro): Find an action, edit its parameters, inspect proposed changes, then execute with cancellation, retry and optional undo. - Conflict Resolver (`conflict-resolver`, Pro): Resolve field-level differences between local and incoming changes, inspect the merged result, and save with retry. - Dashboard (`dashboard`, Pro): A workspace that fits your product. Three navigation layouts, a responsive grid, and composable cards with gentle motion. Try every arrangement in the expanded playground. - Delivery Details (`delivery-details`, Pro): A compact checkout step for contact, address and delivery method, with saving, confirmation and retry states. - FAQ Section (`faq-section`): Questions and answers beside a heading, with a search that filters as you type and a closing line for anything else. - Import Workbench (`import-workbench`, Pro): Bring in CSV data, map columns, preview transformations, correct invalid cells and import only after validation passes. - Metrics Dashboard (`metrics-dashboard`, Pro): A dense analytics view: four headline numbers that double as tabs, one chart you can inspect and compare against the previous period, and ranked lists of top pages and sources. - Notification Inbox (`notification-inbox`, Pro): Grouped updates with unread filtering, individual and batch read actions, undo and a preferences slot. - Order Summary (`order-summary`, Pro): The step before payment: a promo code chip, discount and tax lines, a rolling total and a stateful pay button. - Pricing Table (`pricing-table`, Pro): Three plans side by side with a monthly and yearly switch. Prices roll to their new values when the billing period changes. - Review Contact Sheet (`review-contact-sheet`, Pro): Select assets, compare aligned images, annotate a single image, approve or reject selections, and export a review with comments. - Scenario Workbench (`scenario-workbench`, Pro): Adjust model assumptions, pin a baseline, and compare calculated metrics and projection data in a chart and table. - Sign In (`sign-in`): A sign-in panel that also creates accounts, with provider buttons, validation and progress states. The preview sends nothing anywhere. - Stat Tiles (`stat-tiles`, Pro): A row of headline numbers, each with its trend line and change, that roll to new values when the range changes. - Testimonial Wall (`testimonial-wall`, Pro): A heading over rows of quotes that drift past in opposite directions and slow down under the pointer. - Upload Manager (`upload-manager`, Pro): A file queue with raster previews, per-file progress, cancellation, retry and application-owned upload transport. ## Conventions - `className` styles the root element and is merged last. Do not wrap components just to add spacing. - Stateful components work controlled or uncontrolled: `value` / `defaultValue` / `onValueChange`, or `open` / `defaultOpen` / `onOpenChange`. - Variants and sizes are string unions named `variant` and `size`. Use one primary button per view. - Every part has a `data-slot` attribute for styling from a parent. - Colors come from the shadcn CSS variables (`bg-background`, `text-muted-foreground`, `border-border`, `ring`). Do not hard-code colors. - Motion uses the shared springs in `lib/motion.ts` and already respects reduced motion. Do not add new animation libraries. - Keep loading, empty and error states. Every component documents them; wire them to real data instead of removing them. - Assemblies that ship sample data accept it as props (for example `items` or `plans`). Pass the project's data instead of editing the samples. # Components ## Actions ### Button The base action. Five variants, a loading state that keeps focus and width, a press response, and a hold-to-confirm mode for actions that should not fire by accident. Page: https://latheui.dev/components/button Install: npx shadcn@latest add @latheui/button #### Usage ```tsx import { Button } from "@/components/ui/button" export function SaveButton({ saving, onSave }: { saving: boolean; onSave: () => void }) { return ( ) } ``` #### Props of Button Also accepts every native button attribute, including ref. - `variant`: `"primary" | "secondary" | "outline" | "ghost" | "destructive"`, default `"primary"`. Visual weight. Use one primary button per view. - `size`: `"sm" | "md" | "lg" | "icon" | "icon-sm"`, default `"md"`. Height and padding. The icon sizes are square. - `morphWidth`: `boolean`, default `true`. Animate changes in the intrinsic label width; disabled by reduced motion. - `loading`: `boolean`, default `false`. Shows a spinner, sets aria-busy, and ignores clicks. Ignored with asChild. - `asChild`: `boolean`, default `false`. Merges the button styles and props onto the single child element. - `onHoldConfirm`: `() => void`. Makes it a hold-to-confirm button, used instead of onClick. Runs once the button has been held for holdDuration; releasing early cancels. Destructive buttons fill red, the rest fill with the primary color. - `holdDuration`: `number`, default `1200`. Milliseconds to hold, with onHoldConfirm. - `confirmedLabel`: `ReactNode`, default `"Done"`. Label shown briefly after a hold completes. - `type`: `"button" | "submit" | "reset"`, default `"button"`. Defaults to button so it never submits a form by accident. #### Keyboard - Enter / Space: Activates the button. With onHoldConfirm, hold either key to fill; releasing early cancels. - Escape: With onHoldConfirm: cancels a hold in progress. #### Accessibility - Renders a native button, so the role, focus and keyboard behavior come from the browser. - Loading sets aria-busy and aria-disabled rather than disabled. The button keeps focus, so keyboard users are not dropped to the top of the page when an action starts. - Icon-only buttons have no text. Give them an aria-label. - disabled removes the button from the tab order, so keyboard users cannot land on it. If people need to know why an action is unavailable, say so next to the button. - The focus ring uses the ring token with a 2px offset and is only shown for keyboard focus. - Hold to confirm: screen readers and switch devices usually send a single click and cannot hold. That click starts the full countdown, and Escape or moving focus away cancels it. Completion is announced through a status region. - A hold button's label must say that holding is required: “Hold to delete”, not “Delete”. For irreversible, high-stakes actions, a dialog that states the consequences is still the safer choice. #### Motion - Pressing scales the button to 96% in 90ms. Releasing brings it back over 420ms with a slight overshoot. - With reduced motion, the press scale and color transitions are removed. The loading spinner still turns, because it communicates state. - A hold fills left to right in step with the hold and retreats in 250ms on release. The fill is progress feedback, so it stays with reduced motion. #### Customization ##### Override with classes className is merged last, so utilities win over the variant. ```tsx ``` ##### Add a variant Variants are a plain object at the top of the file. Add a key and it is typed automatically. ```tsx const variants = { // ... brand: "bg-chart-1 text-white hover:bg-chart-1/90", } as const ``` ### Copy Button Copies text to the clipboard, swaps its icon to a check, and announces it to screen readers. Page: https://latheui.dev/components/copy-button Install: npx shadcn@latest add @latheui/copy-button #### Usage ```tsx import { CopyButton } from "@/components/ui/copy-button" export function ApiKey({ value }: { value: string }) { return } ``` #### Props of CopyButton Also accepts Button props such as variant, size and className. - `value` (required): `string`. The text written to the clipboard. - `label`: `string`, default `"Copy"`. Accessible name when the button has no children. - `copiedLabel`: `ReactNode`. Visible text after copying, for example “Copied”. Used with children. - `timeout`: `number`, default `1600`. Milliseconds before the icon returns to idle. - `onCopy`: `(value: string) => void`. Called after a successful copy. - `onCopyError`: `(error: unknown) => void`. Called when the clipboard write is rejected. - `variant`: `ButtonProps["variant"]`, default `"ghost"`. Passed to Button. - `size`: `ButtonProps["size"]`, default `"icon", or "md" with children`. Passed to Button. #### Keyboard - Enter / Space: Copies the value. #### Accessibility - The result is announced through a visually hidden status region: “Copied” or “Copy failed”. - The accessible name stays the same while the icon and label change, so the button does not rename itself mid-interaction. The confirmation is announced by the status region instead. - A failed copy (for example a denied permission or an insecure origin) shows an error icon in the destructive color instead of failing silently. #### Motion - The icon cross-fades and scales with the snappy spring. - With copiedLabel, the label rolls up and the confirmation rolls in from below. - With reduced motion, the icon and label swap without moving. #### Customization ##### Report failures The clipboard API needs a secure context. Handle the error if copying matters to the flow. ```tsx toast.error("Could not copy. Select the text and copy it manually.")} /> ``` ### Magnetic (Pro) Wrap a button and it leans toward a nearby pointer, then gives a soft press when clicked. Page: https://latheui.dev/components/magnetic Install: npx shadcn@latest add @latheui/magnetic #### Usage ```tsx import { Magnetic } from "@/components/ui/magnetic" import { Button } from "@/components/ui/button" export function Cta() { return ( ) } ``` #### Props of Magnetic Also accepts native div attributes. It renders an inline-block wrapper. - `strength`: `number`, default `0.22`. Share of the pointer's distance the content follows. 0 stays put, 1 sticks to the pointer. - `range`: `number`, default `60`. How far past its edges the pull reaches, in pixels. #### Accessibility - Only the wrapper moves. The button inside keeps its role, name, focus and keyboard behavior. - The pull follows a mouse or pen. Touch gets the press, without the pull. #### Motion - The content follows the latest pointer position once per animation frame with quick, refresh-rate-independent smoothing, then eases home when the pointer leaves the range. - A press scales it to 90% and back over 500ms without replacing its pointer offset. - With reduced motion, nothing moves. #### Customization ##### A stronger, shorter pull ```tsx ``` ## Inputs ### Calendar (Pro) A month grid for picking one date, with full keyboard navigation, range limits and disabled days. Page: https://latheui.dev/components/calendar Install: npx shadcn@latest add @latheui/calendar #### Usage ```tsx import { Calendar } from "@/components/ui/calendar" export function DueDate() { const [date, setDate] = React.useState(null) return } ``` #### Props of Calendar Also accepts native div attributes. - `value`: `Date | null`. Controlled selected date. - `defaultValue`: `Date | null`, default `null`. Initial date when uncontrolled. - `onValueChange`: `(date: Date) => void`. Called when a day is selected. - `min`: `Date`. Earliest selectable day. Navigation stops at its month. - `max`: `Date`. Latest selectable day. Navigation stops at its month. - `isDateDisabled`: `(date: Date) => boolean`. Return true for days that cannot be selected. - `weekStartsOn`: `0 | 1`, default `0`. 0 for Sunday, 1 for Monday. - `locale`: `string`, default `"en-US"`. Locale for month and weekday names. - `defaultMonth`: `Date`, default `current month`. Month shown first when there is no value. - `fixedWeeks`: `boolean`, default `true`. Always show six weeks, so the height stays the same between months. - `size`: `"sm" | "default" | "lg"`, default `"default"`. Overall scale. Days are 30px, 36px or 44px, with matching type sizes. - `shape`: `"rounded" | "square" | "circle"`, default `"rounded"`. Shape of the selected and hovered day, and of the month buttons. - `radius`: `number | string`. Corner radius in px, or any CSS length. Overrides the corners set by shape. - `colors`: `{ selected?, selectedForeground?, hover?, today? }`. Any CSS color for each day state. Unset ones follow the theme: primary, primary-foreground, accent and ring. #### Keyboard - ArrowLeft / ArrowRight: Moves one day back or forward. - ArrowUp / ArrowDown: Moves one week back or forward. - Home / End: Moves to the start or end of the week. - PageUp / PageDown: Moves one month back or forward. - Shift+PageUp / Shift+PageDown: Moves one year back or forward. - Enter / Space: Selects the focused day. #### Accessibility - Follows the WAI-ARIA date grid pattern: a grid with one tab stop, moved with the arrow keys. Moving past the edge of a month changes the month and keeps focus on the right day. - Each day's accessible name is the full date, for example “Wednesday, June 17, 2026”, with “today” added where it applies. - The visible month is announced when it changes. - Unavailable days are marked aria-disabled and struck through, so they are distinguishable without color. Keyboard movement is clamped to min and max. - Day buttons sit in 40px columns at the default size, large enough for touch. Use size="lg" for touch-first layouts. - Today's date is read on the client after hydration, so a server-rendered calendar never shows the wrong day near midnight or across time zones. #### Motion - Changing month slides the grid 24px in the direction of travel while it cross-fades, over 220ms. - With reduced motion, months cross-fade without sliding. #### Customization ##### Inside a popover Calendar is the grid only. Put it in your popover and close it on selection to make a date picker. ```tsx { setDate(next) setOpen(false) }} /> ``` ##### Theming with CSS variables The colors and corners are CSS variables, so a stylesheet or a parent can set them for every calendar at once. ```tsx
``` ##### Another locale ```tsx ``` ### Checkbox An ink-filled checkbox with mixed state support and a visible label. Page: https://latheui.dev/components/checkbox Install: npx shadcn@latest add @latheui/checkbox #### Usage ```tsx import { Checkbox } from "@/components/ui/checkbox" export default function Demo(){return
} ``` #### Props of Checkbox - `label` (required): `ReactNode`. Visible label linked to the control. - `checked / defaultChecked`: `boolean | "indeterminate"`. Current or initial state. Clicks toggle between true and false; "indeterminate" is only ever set by your code. - `onCheckedChange`: `(checked: boolean | "indeterminate") => void`. Selection callback. - `name / required / disabled`: `native form props`. Radix form integration. #### Keyboard - Space: Toggle the checkbox. #### Accessibility - The label activates the checkbox. - The mixed state is exposed as aria-checked="mixed". #### Motion - The check and the mixed-state line draw themselves in over 200ms, and draw back out when cleared. - With reduced motion, the marks and the fill change at once. #### Customization ##### Select all Clicks only toggle between checked and unchecked, so the mixed state comes from your code. Derive it from child checkboxes, as in this select-all group. ```tsx "use client" import * as React from "react" import { Checkbox } from "@/components/ui/checkbox" const projects = ["Website", "Mobile app"] export default function Demo() { const [selected, setSelected] = React.useState(["Website"]) const all = selected.length === projects.length ? true : selected.length > 0 ? "indeterminate" : false return (
setSelected(c === true ? projects : [])} />
{projects.map((p) => ( setSelected((s) => (c === true ? [...s, p] : s.filter((x) => x !== p)))} /> ))}
) } ``` ### Combobox A searchable single-choice picker with a clear action and keyboard selection. Page: https://latheui.dev/components/combobox Install: npx shadcn@latest add @latheui/combobox #### Usage ```tsx import { Combobox } from "@/components/ui/combobox" export default function Demo(){return } ``` #### Props of Combobox - `options` (required): `ComboboxOption[]`. Unique values, searchable labels and optional disabled states. - `label` (required): `string`. Visible label and listbox name. - `value / defaultValue`: `string`. Controlled or initial selected value. - `onValueChange`: `(value: string) => void`. Selection callback; an empty string clears selection. - `open / defaultOpen`: `boolean`. Controlled or initial open state of the list. - `onOpenChange`: `(open: boolean) => void`. Called when the list opens or closes. - `name / disabled`: `string / boolean`. Form field name and disabled state. #### Keyboard - ArrowUp / ArrowDown: Navigate enabled results. - Enter: Select the active result. - Escape: Dismiss search. #### Accessibility - Focus stays in the input while aria-activedescendant identifies the active option. Clicking a selected input reopens all options without clearing its label. - Disabled options are skipped. Escape and outside focus close the list. #### Motion - One highlight slides between options over 150ms as the pointer or the arrow keys move. It appears in place when the list opens. - With reduced motion, the highlight moves at once. #### Customization ##### Composition Combine the control with your own layout. Semantic colors follow the active theme. ```tsx import { Combobox } from "@/components/ui/combobox" export default function Demo(){return } ``` ### Date Picker (Pro) A button that shows the chosen date and opens a calendar popover. Picking a day fills it and closes. Page: https://latheui.dev/components/date-picker Install: npx shadcn@latest add @latheui/date-picker #### Usage ```tsx import { DatePicker } from "@/components/ui/date-picker" export function DueDate() { const [date, setDate] = React.useState(null) return } ``` #### Props of DatePicker Also accepts native button attributes, which go to the trigger. - `value`: `Date | null`. Controlled date. - `defaultValue`: `Date | null`, default `null`. Initial date when uncontrolled. - `onValueChange`: `(date: Date) => void`. Called when a day is picked. - `label`: `string`, default `"Date"`. Accessible name, read before the chosen date. - `placeholder`: `string`, default `"Pick a date"`. Shown when no date is chosen. - `format`: `Intl.DateTimeFormatOptions`, default `{ dateStyle: "medium" }`. How the date is written on the button. - `locale`: `string`, default `"en-US"`. Locale for the button and the calendar. - `min`: `Date`. Earliest selectable day. - `max`: `Date`. Latest selectable day. - `isDateDisabled`: `(date: Date) => boolean`. Return true for days that cannot be picked. - `weekStartsOn`: `0 | 1`, default `0`. 0 for Sunday, 1 for Monday. - `defaultMonth`: `Date`. Month shown first when there is no value. - `align`: `"start" | "center" | "end"`, default `"start"`. Edge of the button the popover lines up with. - `name`: `string`. Submits the date with a form as YYYY-MM-DD. #### Keyboard - Enter / Space: Opens the calendar from the button, or picks the focused day. - Arrow keys: Move between days. PageUp and PageDown change month. - Escape: Closes the calendar and returns focus to the button. #### Accessibility - The button's name is the label followed by the chosen date or the placeholder, for example “Due date: Oct 14, 2026”. - It is built on Radix Popover: focus moves into the calendar on open and back to the button on close. - The calendar inside is the Calendar component, with its full keyboard support. - For dates people know by heart, such as a birthday, three plain fields are faster than any picker. #### Motion - The popover scales in from the button with the snappy spring. - With reduced motion it fades. #### Customization ##### Format and limits ```tsx booked.has(date.toDateString())} /> ``` ##### In a form ```tsx
``` ### Email Capture (Pro) An email field and button in one pill, with progress, validation, an error line and a success state. Page: https://latheui.dev/components/email-capture Install: npx shadcn@latest add @latheui/email-capture #### Usage ```tsx import { EmailCapture } from "@/components/ui/email-capture" export function Newsletter() { return ( { const response = await fetch("/api/subscribe", { method: "POST", body: JSON.stringify({ email }) }) if (!response.ok) throw new Error("We could not add you. Try again.") }} /> ) } ``` #### Props of EmailCapture Also accepts native form attributes. - `onSubmit` (required): `(email: string) => void | Promise`. Receives the trimmed address. A pending promise shows progress; a rejection shows an error. - `label`: `string`, default `"Email address"`. Accessible name of the field. - `placeholder`: `string`, default `"you@example.com"`. Placeholder text. - `buttonLabel`: `string`, default `"Subscribe"`. Text of the submit button. - `successMessage`: `ReactNode`, default `"You are on the list."`. Replaces the field after a successful submit. - `errorMessage`: `string`, default `"Something went wrong. Try again."`. Shown when onSubmit fails without an Error message of its own. - `description`: `ReactNode`. Small print under the field. #### Keyboard - Enter: Submits the form from the field. #### Accessibility - The field has an accessible name from label, since a placeholder is not one. - Errors are text under the field, linked to it with aria-describedby and announced as an alert. Focus returns to the field. - While sending, the button reports busy and the field is read-only, not disabled, so focus is not lost. - Success is announced through a status region. - The field asks for an email keyboard and offers the saved address through autocomplete. #### Motion - The arrow swaps for a spinner while sending. On success the pill is replaced by the message and a check that pops in. - With reduced motion the states swap with a fade. #### Customization ##### Wording ```tsx ``` ### Inline Edit (Pro) Edit a value in place, with explicit save and cancel, validation and asynchronous retry. Page: https://latheui.dev/components/inline-edit Install: npx shadcn@latest add @latheui/inline-edit #### Usage ```tsx import { InlineEdit } from "@/components/ui/inline-edit" export default function ProjectName() { return { const response = await fetch("/api/project", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }) if (!response.ok) throw new Error("Could not save. Try again.") }} /> } ``` #### Props of InlineEdit - `label` (required): `string`. Visible label and accessible name for editing actions. - `value / defaultValue`: `string`, default `"" (defaultValue)`. Controlled or initial committed value. Drafts stay local until a successful save. - `onValueChange`: `(value: string) => void`. Called with the trimmed value after saving succeeds. - `onSave`: `(value: string) => void | Promise`. Resolve to commit. Reject with an Error to keep the draft and show its message. Omit to save locally. - `validate`: `(value: string) => string | undefined`. Return an error message to prevent saving. Empty or whitespace-only values are always rejected. - `placeholder`: `string`, default `"Add a value"`. Text shown when the committed value is empty. - `maxLength`: `number`. Native input character limit. - `disabled`: `boolean`, default `false`. Disable editing actions. - `className`: `string`. Style the outer wrapper; native div attributes are forwarded. #### Keyboard - Enter: Open the focused editor, or save a draft. Composition input is preserved. - Escape: Cancel the draft while no save is pending. - Tab: Move through the input, Save and Cancel. Leaving the field keeps the draft open. #### Accessibility - The visible label names the input. Validation errors are linked to it and announced as alerts. - Opening selects the input text. Save and Cancel return focus to the edit button. - Saving makes the input read-only, blocks duplicate submission and announces progress. Disabled controls use native disabled semantics. #### Motion - The shared Button animates its label width with the snappy spring. - Editing surfaces stay in normal flow. Button motion follows the reduced-motion preference. #### Customization ##### Validate before persistence Return a short, actionable message. Validation runs on the trimmed draft before onSave. ```tsx value.length < 3 ? "Use at least three characters." : undefined} onSave={saveTeamName} /> ``` ### Input A text input with leading and trailing slots, plus a Field that wires up label, description and error. Page: https://latheui.dev/components/input Install: npx shadcn@latest add @latheui/input #### Usage ```tsx import { Field, Input } from "@/components/ui/input" export function EmailField({ error }: { error?: string }) { return ( ) } ``` #### Props of Input className styles the bordered wrapper. Every other prop, including ref, goes to the native input. - `size`: `"sm" | "md" | "lg"`, default `"md"`. Control height. Replaces the native size attribute. - `startSlot`: `ReactNode`. Content before the text, such as an icon or a currency symbol. - `endSlot`: `ReactNode`. Content after the text, such as a unit, a shortcut hint or a button. - `invalid`: `boolean`. Marks the input invalid. Set automatically inside a Field that has an error. #### Props of Field Also accepts native div attributes. - `label` (required): `ReactNode`. The visible label, linked to the control. - `description`: `ReactNode`. Helper text under the control. - `error`: `ReactNode`. Error text. Marks the control invalid and replaces the description. - `required`: `boolean`, default `false`. Adds a marker to the label. Also set required on the control. - `children` (required): `ReactNode`. One control, usually an Input. #### Accessibility - Field generates the id and connects the label with htmlFor, so clicking the label focuses the input. - The description or error is linked with aria-describedby, and aria-invalid is set when there is an error. - Errors appear in a polite live region, so they are read when they show up without interrupting typing. - The error state changes the border and the message, not color alone. - On small screens the text is 16px, so iOS does not zoom the page when the input is focused. #### Motion - The message area animates its height over 160ms when an error appears or clears. - With reduced motion, the message swaps instantly. #### Customization ##### A button in the end slot ```tsx Apply } /> ``` ### Linked Allocation (Pro) Move one allocation while the others make room. Lock values, preserve the total, and redistribute the remainder proportionally. Page: https://latheui.dev/components/linked-allocation Install: npx shadcn@latest add @latheui/linked-allocation #### Usage ```tsx "use client" import { LinkedAllocation } from "@/components/ui/linked-allocation" export default function AllocationDemo() { return
} ``` #### Props of LinkedAllocation - `label` (required): `string`. Visible legend for the allocation group. - `value / defaultValue`: `AllocationItem[]`, default `[] (defaultValue)`. Controlled or initial items with unique id, label, nonnegative value and optional locked flag. - `onValueChange`: `(items: AllocationItem[]) => void`. Receives a new array after adjustment or lock changes. - `total`: `number`, default `100`. Required sum of allocation values. Percentage allocations normally use 100. - `disabled`: `boolean`, default `false`. Disable sliders and lock buttons. - `className`: `string`. Classes for the fieldset. #### Keyboard - Arrow keys: Adjust a focused Lathe slider. Locked values remain unchanged. - Tab: Move through the enabled controls. Focus remains visible. #### Accessibility - Lathe controls expose accessible names, states and keyboard behavior. Layouts wrap at narrow widths and use semantic theme tokens. - Lathe sliders and lock buttons provide a keyboard workflow. Invalid totals disable adjustments; the visible message explains why. #### Motion - When one slider moves, the others glide to their recalculated share over 320ms instead of jumping. The slider you drag follows the pointer. - The bar segments resize on the same curve. Reduced motion turns both off. #### Customization ##### Connect your own data Use the typed props to connect application state. The source is installed into your project. ```tsx "use client" import { LinkedAllocation } from "@/components/ui/linked-allocation" export default function AllocationDemo() { return
} ``` ### Number Stepper Adjusts a small number with buttons or arrow keys. Digits roll with the change and nudge at the limits. Page: https://latheui.dev/components/number-stepper Install: npx shadcn@latest add @latheui/number-stepper #### Usage ```tsx import { NumberStepper } from "@/components/ui/number-stepper" export function Quantity() { const [quantity, setQuantity] = React.useState(1) return ( ) } ``` #### Props of NumberStepper Also accepts native div attributes. - `label` (required): `string`. Accessible name for the value. - `value`: `number`. Controlled value. - `defaultValue`: `number`, default `0`. Initial value when uncontrolled, clamped to the limits. - `onValueChange`: `(value: number) => void`. Called with the new value. - `min`: `number`, default `-Infinity`. Lowest allowed value. - `max`: `number`, default `Infinity`. Highest allowed value. - `step`: `number`, default `1`. Amount added or removed per press. - `format`: `(value: number) => string`, default `String`. Formats the displayed value. Also used for the spoken value. - `disabled`: `boolean`, default `false`. Prevents interaction. - `name`: `string`. Adds a hidden input so the value submits with a form. #### Keyboard - ArrowUp / ArrowRight: Increases by one step. - ArrowDown / ArrowLeft: Decreases by one step. - PageUp / PageDown: Changes by ten steps. - Home / End: Jumps to the minimum or maximum, when one is set. #### Accessibility - The value is a spinbutton with aria-valuenow, aria-valuemin, aria-valuemax and a formatted aria-valuetext. - It is a single tab stop. The plus and minus buttons are for pointers and are left out of the tab order, as the keyboard has the arrow keys. - Each button dims and is marked aria-disabled at its limit, so the state is visible without relying on the nudge animation. - It does not accept typed input. For large or precise numbers, use a text input. #### Motion - The new value slides in from the direction of change with the snappy spring. - At a limit, the value moves 5px toward the blocked side and returns. - With reduced motion, values cross-fade and the nudge is skipped. #### Customization ##### Wider value The value area grows with its content. Set a minimum width to stop the buttons moving when the digit count changes. ```tsx ``` ### OTP Input (Pro) A one-time code field drawn as slots over one input, so paste, SMS autofill and backspace all work. Page: https://latheui.dev/components/otp-input Install: npx shadcn@latest add @latheui/otp-input #### Usage ```tsx import { OtpInput } from "@/components/ui/otp-input" export function Verify({ onVerify }: { onVerify: (code: string) => void }) { return } ``` #### Props of OtpInput Also accepts native input attributes such as name, autoFocus and required. They go to the real input. - `length`: `number`, default `6`. Number of characters. - `value`: `string`. Controlled value. - `defaultValue`: `string`, default `""`. Initial value when uncontrolled. - `onValueChange`: `(value: string) => void`. Called on every change. - `onComplete`: `(value: string) => void`. Called once every slot is filled. - `mode`: `"numeric" | "alphanumeric"`, default `"numeric"`. Accepted characters. Others are dropped, including from pasted text. - `groupSize`: `number`. Adds a divider after every this many slots. - `mask`: `boolean`, default `false`. Shows dots instead of characters. - `invalid`: `boolean`, default `false`. Marks the code as wrong. - `label`: `string`, default `"Verification code"`. Accessible name. - `disabled`: `boolean`. Disables the field. #### Keyboard - 0-9 / A-Z: Fills the next slot. - Backspace: Clears the last filled slot. - Ctrl+V / ⌘V: Pastes a code. Characters that are not allowed are dropped. #### Accessibility - There is one real input, so screen readers announce one field with one name instead of six unlabeled boxes. - autocomplete is one-time-code, so phones offer the code from an incoming message. - The numeric mode asks for a number keypad on touch keyboards. - invalid sets aria-invalid. Say what is wrong in text next to the field as well; a red border alone is not a message. - The caret always sits after the last character, so the arrow keys are ignored. That keeps what is typed and what is shown in step. #### Motion - Each character pops in with the snappy spring. The active slot shows a blinking caret. - With reduced motion characters appear without moving and the caret is steady. #### Customization ##### Change the length ```tsx ``` ##### Verify, and show a wrong code ```tsx const [code, setCode] = React.useState("") const [wrong, setWrong] = React.useState(false) { setCode(next) setWrong(false) }} onComplete={async (value) => setWrong(!(await verify(value)))} /> ``` ### Password Field A password input with a reveal toggle and a local strength indicator. Page: https://latheui.dev/components/password-field Install: npx shadcn@latest add @latheui/password-field #### Usage ```tsx import { PasswordField } from "@/components/ui/password-field" export default function Demo(){return
} ``` #### Props of Password Field - `label` (required): `string`. Visible field label. - `strength`: `boolean`, default `true`. Show the local heuristic strength meter. - `colors`: `string[]`. One CSS color per strength bar, weakest first. The last repeats if there are fewer than four. Defaults to the primary color. - `value / defaultValue`: `string`. Controlled or initial password. - `autoComplete`: `string`, default `"new-password"`. Use current-password for sign-in forms. #### Accessibility - The reveal control has an accessible name and pressed state. - Strength is written as text as well as segments. - This local heuristic is guidance; applications must enforce their own password policy. #### Motion - Each strength bar fills from the left over 300ms and empties back toward it. Bars that change together go one after another, 60ms apart. - With reduced motion, the bars change at once. #### Customization ##### Composition Combine the control with your own layout. Semantic colors follow the active theme. ```tsx import { PasswordField } from "@/components/ui/password-field" export default function Demo(){return
} ``` ### Precision Scrub Field Adjust a number by dragging a ruler, fine-tune with modifiers, type an exact value, or change display units without changing the canonical value. Page: https://latheui.dev/components/precision-scrub-field Install: npx shadcn@latest add @latheui/precision-scrub-field #### Usage ```tsx "use client" import * as React from "react" import { PrecisionScrubField } from "@/components/ui/precision-scrub-field" const units = [{ label: "px", factor: 1 }, { label: "rem", factor: 16 }] export default function PrecisionDemo() { const [width, setWidth] = React.useState(160) return
} ``` #### Props of PrecisionScrubField - `label` (required): `string`. Visible input label and accessible name for scrub and reset actions. - `value / defaultValue`: `number`, default `0 (defaultValue)`. Controlled or initial value in canonical units. - `onValueChange`: `(value: number) => void`. Receives canonical values while dragging, typing, resetting or using keys. - `onValueCommit`: `(value: number) => void`. Receives the value on pointer release, input blur, keyboard adjustment or reset. - `min / max`: `number`, default `-Infinity / Infinity`. Bounds in canonical units. - `step`: `number`, default `1`. Positive step in canonical units; Shift uses one tenth for fine adjustment. - `units`: `readonly ScrubUnit[]`. Display units with label and positive conversion factor, e.g. px = 1 and rem = 16. The first unit is initially selected. - `disabled`: `boolean`, default `false`. Disable the input, scrub handle, units and reset. - `className`: `string`. Classes for the outer wrapper. #### Keyboard - Arrow keys: Adjust from the scrub handle; Shift uses a tenth step and Alt ten steps. - Tab: Move through the enabled controls. Focus remains visible. #### Accessibility - Lathe controls expose accessible names, states and keyboard behavior. Layouts wrap at narrow widths and use semantic theme tokens. #### Motion - While scrubbing, the value chases the pointer on a short ease (about 110ms to catch up), so a fast flick rolls through the numbers instead of jumping. onValueCommit fires once it arrives. - A tick ruler under the field slides with the value as you scrub. - With reduced motion, the value follows the pointer directly. #### Customization ##### Connect your own data Use the typed props to connect application state. The source is installed into your project. ```tsx "use client" import * as React from "react" import { PrecisionScrubField } from "@/components/ui/precision-scrub-field" const units = [{ label: "px", factor: 1 }, { label: "rem", factor: 16 }] export default function PrecisionDemo() { const [width, setWidth] = React.useState(160) return
} ``` ### Radio Group A single-choice list of cards with sliding, fading, scaling, or instant selection and adjustable timing. Page: https://latheui.dev/components/radio-group Install: npx shadcn@latest add @latheui/radio-group #### Usage ```tsx import { RadioGroup } from "@/components/ui/radio-group" export default function Demo(){return } ``` #### Props of Radio Group - `options` (required): `RadioOption[]`. Labels, descriptions and disabled options. - `label` (required): `string`. Accessible group name. - `value / defaultValue`: `string`. Controlled or initial selection. - `animation`: `"slide" | "fade" | "scale" | "none"`, default `"slide"`. How the selected card is highlighted. Scale fades and grows in place; none also disables the dot animation. - `slideDuration`: `number`, default `Smooth spring`. Slide duration in milliseconds. When set, uses a timed ease-out instead of the default spring. Zero moves immediately. - `fadeInDuration`: `number`, default `200`. Milliseconds for the new highlight to appear in fade and scale modes. Also controls the scale-in timing. - `fadeOutDuration`: `number`, default `200`. Milliseconds for the old highlight to disappear in fade and scale modes. Also controls the scale-out timing. - `dotDuration`: `number`, default `200`. Milliseconds for the radio dot's opacity and size, and its circle's border color. Independent of the highlight timing. - `onValueChange`: `(value: string) => void`. Selection callback. #### Keyboard - Arrow keys: Move and select within the group. #### Accessibility - Radix provides roving focus and radio semantics. - Every option has a visible clickable label. #### Motion - Slide shares one outline between cards, using the smooth spring by default or a timed ease-out when slideDuration is set. - Fade crossfades the highlights in place. Scale adds a subtle growth from 96% to full size. Both use fadeInDuration and fadeOutDuration independently. - The dot stays inside its own circle and uses dotDuration independently of the card highlight. - All durations are in milliseconds; zero is instant and negative values are clamped to zero. With reduced motion or animation="none", selection updates immediately. #### Customization ##### Selection animation and timing Use fade for a stationary highlight, scale for a subtle pop, or none for instant selection. Set slideDuration to adjust the sliding outline. Fade-in, fade-out, and dot durations are independent, in milliseconds. ```tsx ``` ##### Composition Combine the control with your own layout. Semantic colors follow the active theme. ```tsx import { RadioGroup } from "@/components/ui/radio-group" export default function Demo(){return } ``` ### Segmented Control Picks one option from a small set. Behaves as a radio group, with a thumb that slides to the selection. Page: https://latheui.dev/components/segmented-control Install: npx shadcn@latest add @latheui/segmented-control #### Usage ```tsx import { SegmentedControl } from "@/components/ui/segmented-control" export function RangePicker() { const [range, setRange] = React.useState("week") return ( ) } ``` #### Props of SegmentedControl Generic over the option values, so onValueChange is typed to your union. Also accepts native div attributes. - `options` (required): `SegmentedOption[] | SegmentedOption[][]`. The segments, in order: { value, label, disabled? }. An array of arrays makes sections with a divider between. - `value`: `T`. Controlled selection. - `defaultValue`: `T`, default `first enabled option`. Initial selection when uncontrolled. - `onValueChange`: `(value: T) => void`. Called when the selection changes. - `size`: `"sm" | "md" | "lg"`, default `"md"`. Segment height: 28, 32 or 40px. - `fullWidth`: `boolean`, default `false`. Stretches to the container with equal-width segments. - `disabled`: `boolean`, default `false`. Disables every segment. - `name`: `string`. Adds a hidden input so the value submits with a form. - `inset`: `number`, default `2`. Space between the track's edge and the thumb, in px. - `gap`: `number`, default `2`. Space between segments, in px. - `radius`: `number | string`, default `8`. Track corner radius, in px or any CSS length. Use 999 for a pill. The thumb's radius is this less the inset. - `dividers`: `boolean`, default `false`. A hairline between every pair of segments, hidden beside the selected one. - `curve`: `Curve`, default `snappy spring`. How the thumb moves. Any curve from lib/animate or the Animation Lab: springs, bezier, elastic, steps and more. - `duration`: `number`. Milliseconds, for curves that are not springs. - `classNames`: `{ thumb?: string; segment?: string; divider?: string }`. Classes for the thumb, every segment and the dividers. #### Keyboard - Tab: Moves focus to the selected segment, then out of the group. - ArrowRight / ArrowDown: Selects the next enabled segment, wrapping to the first. - ArrowLeft / ArrowUp: Selects the previous enabled segment, wrapping to the last. - Home / End: Selects the first or last enabled segment. #### Accessibility - Uses the radiogroup and radio roles with a roving tab stop, so it is one stop in the tab order. - Pass aria-label or aria-labelledby to name the group. - Use it for two to five short options, or sections of them. For more, or for options that switch panels, use Select or Tabs. - Section dividers and hairlines are decorative and hidden from assistive tech; arrow keys move across every section. - If labels are icons only, give each icon an accessible name. #### Motion - The thumb moves to the selected segment with a snappy spring (stiffness 900, damping 48), or with the curve you pass. - One thumb moves in the track's local coordinates, beneath every label. Moving, scrolling, or resizing the control keeps it attached; only selection changes animate. - With reduced motion, the thumb jumps. #### Customization ##### A pill with a colored thumb ```tsx ``` ##### A curve from the Animation Lab Copy a curve from the Lab's JavaScript export and pass it as curve. ```tsx ``` ##### Typed values ```tsx type View = "grid" | "list" const [view, setView] = React.useState("grid") aria-label="View" value={view} onValueChange={setView} options={[ { value: "grid", label: "Grid" }, { value: "list", label: "List" }, ]} /> ``` ### Select A compact option picker with typeahead, keyboard navigation, a highlight that slides between options, and an ink checkmark. Page: https://latheui.dev/components/select Install: npx shadcn@latest add @latheui/select #### Usage ```tsx import { Select } from "@/components/ui/select" export default function Demo() { return

Workspace

} ``` ### Slider A precise range control with one or more thumbs and a live value readout. Page: https://latheui.dev/components/slider Install: npx shadcn@latest add @latheui/slider #### Usage ```tsx import { Slider } from "@/components/ui/slider" export default function Demo(){return
v+"%"}/>"$"+v}/>
} ``` #### Props of Slider - `label` (required): `string`. Visible and accessible name. - `value / defaultValue`: `number[]`, default `[50]`. One thumb per value; two values create a range. - `onValueChange / onValueCommit`: `(value: number[]) => void`. Live changes and committed changes. - `min / max / step`: `number`. Range and precision. Defaults to 0, 100, 1. - `formatValue`: `(value: number) => string`. Visible and spoken value formatting. - `editable`: `boolean`, default `false`. Single thumb only: the readout becomes a field you can type a value into. - `animateValue`: `boolean`, default `false`. Glide to values set from outside, such as a linked control or a timeline clip. A drag on this slider still follows the pointer. #### Keyboard - Arrow keys / Home / End: Adjust a thumb or move to its boundary. #### Accessibility - Each thumb has a label and formatted aria-valuetext. - Radix supplies keyboard control and form integration. #### Motion - Clicking the track sends the thumb there over 320ms, speeding up and then slowing down. - While dragging, the thumb chases the pointer on a 110ms ease-out: quick enough to feel direct, slow enough to see it glide. - With reduced motion, the thumb moves at once, for clicks and drags alike. #### Customization ##### Composition Combine the control with your own layout. Semantic colors follow the active theme. ```tsx import { Slider } from "@/components/ui/slider" export default function Demo(){return
v+"%"}/>"$"+v}/>
} ``` ### Spring Switch (Pro) A big, springy on and off switch. Click it or swipe the thumb across, and choose how much it bounces. Page: https://latheui.dev/components/spring-switch Install: npx shadcn@latest add @latheui/spring-switch #### Usage ```tsx import { SpringSwitch } from "@/components/ui/spring-switch" export function Setting() { return } ``` #### Props of SpringSwitch Also accepts native button attributes, such as id, name and disabled. - `checked`: `boolean`. Controlled state. - `defaultChecked`: `boolean`, default `false`. Initial state when uncontrolled. - `onCheckedChange`: `(checked: boolean) => void`. Called when the switch is clicked, swiped or set from the keyboard. - `damping`: `number`, default `18`. Spring damping. Lower is bouncier: 10 is playful, 32 calm. - `icons`: `{ on?: ReactNode; off?: ReactNode }`. Shown inside the thumb for each state. #### Keyboard - Space / Enter: Toggles the switch. - ArrowRight: Turns it on. - ArrowLeft: Turns it off. #### Accessibility - A native button with role switch and aria-checked, so it is announced as on or off. - Always label it, with a label element pointing at its id or with aria-label. - Swiping is an extra. Everything it does can be done with a click or the keyboard. - Vertical scrolling still works when a swipe starts on the switch. #### Motion - The thumb springs across with a stiffness of 380. Damping sets how far it overshoots and how often it bounces. - A swipe hands its speed to the spring, so a hard flick lands harder. - With reduced motion, the thumb jumps to its place. #### Customization ##### Change the on color ```tsx ``` ##### Change the size The track is sized from two CSS variables: the thumb and the padding around it. ```tsx ``` ### Switch Turns a setting on or off immediately. Click it, or swipe the thumb across. It stretches while pressed and springs into place. Page: https://latheui.dev/components/switch Install: npx shadcn@latest add @latheui/switch #### Usage ```tsx import { Switch } from "@/components/ui/switch" export function Setting() { return (
) } ``` #### Props of Switch Also accepts Radix Switch.Root props such as name, value, required and disabled. - `checked`: `boolean`. Controlled state. - `defaultChecked`: `boolean`, default `false`. Initial state when uncontrolled. - `onCheckedChange`: `(checked: boolean) => void`. Called when the switch is toggled. - `size`: `"sm" | "md"`, default `"md"`. Track size. - `disabled`: `boolean`, default `false`. Prevents interaction. #### Keyboard - Space / Enter: Toggles the switch. #### Accessibility - Built on Radix Switch: a button with role switch and aria-checked. - Always label it, with a label element pointing at its id or with aria-label. - A switch takes effect immediately. If the change only applies after a Save button, use a checkbox. - On and off are shown by thumb position as well as color. - Swiping is an extra: a click, a tap or the keyboard still toggle it. Vertical page scrolling still works when a touch starts on the switch. #### Motion - The thumb travels with the snappy spring and widens by 4px while the switch is pressed. - Swiping drags the thumb under the pointer. Let go past the middle and it switches; short of it, it springs back. A swipe that ends on the side it started from changes nothing. - With reduced motion, the thumb jumps and does not stretch. #### Customization ##### Change the on color ```tsx ``` ### Tag Input A token editor with duplicate prevention, removable chips and a keyboard workflow. Page: https://latheui.dev/components/tag-input Install: npx shadcn@latest add @latheui/tag-input #### Usage ```tsx import { TagInput } from "@/components/ui/tag-input" export default function Demo(){return } ``` #### Props of Tag Input - `label` (required): `string`. Visible input label. - `value / defaultValue`: `string[]`. Controlled or initial tags. - `onValueChange`: `(tags: string[]) => void`. Called after adding or removing a tag. - `maxTags`: `number`. Maximum tag count. - `name`: `string`. Submit one hidden form value per tag. #### Keyboard - Enter / ,: Add the typed tag. - Backspace: Remove the last tag when the input is empty. #### Accessibility - Removable chips are named buttons. - Additions, removals and duplicate errors are announced. - Composition input is preserved. #### Customization ##### Composition Combine the control with your own layout. Semantic colors follow the active theme. ```tsx import { TagInput } from "@/components/ui/tag-input" export default function Demo(){return } ``` ### Textarea A paper-like multiline field with an optional character counter. Page: https://latheui.dev/components/textarea Install: npx shadcn@latest add @latheui/textarea #### Usage ```tsx import { Textarea } from "@/components/ui/textarea" export default function Demo(){return