Skip to content

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-ui with 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, the cn helper in lib/utils and the color tokens.

If you have not used the shadcn CLI in this project yet, initialize it first.

pnpm dlx shadcn@latest init

Add 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 init

Or add it to components.json yourself:

components.json
{
  "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/tabs

The 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.

lib/motion.ts
"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,
  }
}
hooks/use-controllable-state.ts
"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/theme

App-level setup

Two components need something rendered once, near the root of your app.

app/providers.tsx
"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>
  )
}
  • Toaster is required if you call toast().
  • TooltipProvider is optional. It lets neighbouring tooltips share their delay.
  • MotionConfig is optional. Components already respect reduced motion on their own; this extends the same behavior to your own Motion code.