Installation
Set up the shadcn CLI once, then add components one at a time. Each one arrives as source in your project.
Requirements
- React 19 and TypeScript.
- Tailwind CSS v4.
- Any shadcn style. Components that need a primitive use Radix and bring
radix-uiwith them, so they also work in projects set up with the Base UI styles. - A project initialized for the shadcn CLI, which provides
components.json, thecnhelper inlib/utilsand the color tokens.
If you have not used the shadcn CLI in this project yet, initialize it first.
pnpm dlx shadcn@latest initAdd the registry
Once per project, add the Lathe registry so the CLI knows where @latheui items come from. In your project's root, next to components.json:
pnpm dlx latheui initOr add it to components.json yourself:
{
"registries": {
"@latheui": "https://latheui.dev/api/registry/{name}.json"
}
}Add a component
Every component page shows its command. Free and Pro items install the same way, by name.
pnpm dlx shadcn@latest add @latheui/button @latheui/tabsThe CLI writes the files to components/ui, installs the npm packages they import, and adds any other Lathe items they depend on.
No setup at all? A free item also installs from its full address: npx shadcn@latest add https://latheui.dev/r/button.json
Shared files
Two small files are shared by most components. The CLI adds them automatically. If you install by hand, copy them first.
"use client"
import { useReducedMotion, type Transition } from "motion/react"
/**
* Springs, by what is moving rather than how it should feel:
* - snappy: small controls (thumbs, indicators, checkmarks)
* - smooth: surfaces that enter or change layout (popovers, dialogs, list items)
* - gentle: large surfaces that travel far (drawers, lightboxes)
*/
export const spring = {
hover: { type: "spring", stiffness: 900, damping: 48, mass: 0.65 },
snappy: { type: "spring", stiffness: 520, damping: 38, mass: 0.8 },
smooth: { type: "spring", stiffness: 320, damping: 32, mass: 1 },
gentle: { type: "spring", stiffness: 210, damping: 28, mass: 1 },
} as const satisfies Record<string, Transition>
/** Durations in seconds for non-spring transitions (opacity, color). */
export const duration = {
instant: 0.1,
fast: 0.16,
base: 0.22,
slow: 0.36,
} as const
export const ease = {
out: [0.22, 1, 0.36, 1],
in: [0.5, 0, 0.75, 0],
inOut: [0.65, 0, 0.35, 1],
} as const
export const fade = {
duration: duration.fast,
ease: ease.out,
} as const satisfies Transition
const none: Transition = { duration: 0 }
/**
* Returns `transition` unchanged, or an instant transition when the user
* prefers reduced motion. Use it for anything that moves or scales; plain
* opacity fades can stay.
*/
export function useMotionSafe() {
const reduced = useReducedMotion() ?? false
return {
reduced,
safe: <T extends Transition>(transition: T): Transition =>
reduced ? none : transition,
}
}"use client"
import * as React from "react"
type Options<T> = {
value?: T
defaultValue: T
onChange?: (value: T) => void
}
/** State that follows `value` when it is provided and manages itself otherwise. */
export function useControllableState<T>({
value,
defaultValue,
onChange,
}: Options<T>): [T, (next: T) => void] {
const [internal, setInternal] = React.useState(defaultValue)
const controlled = value !== undefined
const current = controlled ? value : internal
const onChangeRef = React.useRef(onChange)
React.useEffect(() => {
onChangeRef.current = onChange
})
const set = React.useCallback(
(next: T) => {
if (!controlled) setInternal(next)
onChangeRef.current?.(next)
},
[controlled]
)
return [current, set]
}Theme
Components use the standard shadcn tokens, so they work with your current theme. To use the Lathe palette shown on this site, add the theme item. See Theming for the values.
pnpm dlx shadcn@latest add @latheui/themeApp-level setup
Two components need something rendered once, near the root of your app.
"use client"
import { MotionConfig } from "motion/react"
import { Toaster } from "@/components/ui/toast"
import { TooltipProvider } from "@/components/ui/tooltip"
export function Providers({ children }: { children: React.ReactNode }) {
return (
<MotionConfig reducedMotion="user">
<TooltipProvider>{children}</TooltipProvider>
<Toaster />
</MotionConfig>
)
}Toasteris required if you calltoast().TooltipProvideris optional. It lets neighbouring tooltips share their delay.MotionConfigis optional. Components already respect reduced motion on their own; this extends the same behavior to your own Motion code.