Skip to content

Get started

Installation

Set up Desyne in a Next.js, Vite or existing shadcn project, in a monorepo, or by copying files by hand.

Desyne installs through the shadcn CLI. The CLI reads the registry, copies component source into your project, installs npm dependencies and adds any CSS variables a component needs.

Requirements

React 19, Tailwind CSS v4 and a @/* import alias. Components import @/lib/utils, @/lib/primitive and @/components/ui/*, the same paths a shadcn project already uses.

Pick your setup

Create the app

Terminal
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app

Initialise shadcn

Terminal
npx shadcn@latest init

This writes components.json, lib/utils.ts and the base tokens in app/globals.css.

Register the namespace

Add the Desyne registry to components.json:

components.jsonjson
{
  "registries": {
    "@desyne": "https://desyne.dev/r/{name}.json"
  }
}

Add a component

Terminal
npx shadcn@latest add @desyne/button
app/page.tsxtsx
import { Button } from "@/components/ui/button";

export default function Page() {
  return <Button onPress={() => alert("Hello")}>Click me</Button>;
}

Components that use hooks or React Aria are marked "use client", so they work in App Router pages without extra wrappers. See Server components.

Monorepos

In a Turborepo or pnpm/Bun workspace, keep components in a shared package and point each app at it.

  1. Run npx shadcn@latest init in the shared UI package and in each app, so every workspace has its own components.json with the @desyne registry entry.

  2. Install into the shared package with --cwd (-c):

    Terminal
    npx shadcn@latest add @desyne/dialog -c packages/ui
  3. In each app, map the aliases the components import to the shared package:

    apps/web/tsconfig.jsonjson
    {
      "compilerOptions": {
        "paths": {
          "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
          "@/lib/utils": ["../../packages/ui/src/lib/utils"],
          "@/lib/primitive": ["../../packages/ui/src/lib/primitive"],
          "@/*": ["./*"]
        }
      }
    }
  4. Tell Tailwind to scan the package and, in Next.js, transpile it:

    apps/web/app/globals.csscss
    @import "tailwindcss";
    @source "../../../packages/ui/src";
    apps/web/next.config.mjsjs
    export default { transpilePackages: ["@acme/ui"] };

This is exactly how this documentation site consumes the library.

Manual installation

You can skip the CLI and copy files yourself. Every component page has a Manual tab with its source, its npm dependencies and the other components it imports.

Install the shared dependencies

Terminal
npm install react-aria-components tailwind-variants clsx tailwind-merge lucide-react tw-animate-css

Some components need more (sonner for Toast, recharts for Chart, embla-carousel-react for Carousel, input-otp for Input OTP). Their pages list them.

Add the helpers

lib/utils.tsts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Then copy lib/primitive.ts from the registry (the files[0].content field) or from any component's Manual tab.

Add the tokens

Your global CSS needs Tailwind, the animation utilities and the tokens:

app/globals.csscss
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

:root {
  --radius: 0.625rem;
  --background: oklch(0.984 0.002 286);
  --foreground: oklch(0.205 0.006 286);
  /* …the rest of the tokens */
}

The full set, with dark values and the @theme inline mapping, is on Colors & tokens.

Copy components

Copy each component into components/ui/ and fix the imports if your aliases differ.

Check it works

tsx
"use client";

import { Form } from "react-aria-components";
import { Button } from "@/components/ui/button";
import { TextField } from "@/components/ui/text-field";

export function Smoke() {
  return (
    <Form className="flex max-w-xs flex-col gap-4">
      <TextField label="Email" name="email" type="email" isRequired />
      <Button type="submit">Subscribe</Button>
    </Form>
  );
}

Tab to the field: you should see the soft colored focus ring. Submit it empty: focus moves to the field and the required-field message appears under it.

Next steps