Skip to main content

Consumer Setup

This guide begins after @thyris/ui has been added to the application. If the application also consumes @thyris/ui-patterns, keep both packages on the same release version.

Configure Tailwind​

The consuming application must scan the installed package output and map the semantic CSS variables to Tailwind utilities.

import type {Config} from "tailwindcss"

const config: Config = {
darkMode: "class",
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/@thyris/ui/dist/**/*.{js,mjs}",
"./node_modules/@thyris/ui-patterns/dist/**/*.{js,mjs}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [require("tailwindcss-animate")],
}

export default config

If only @thyris/ui is used, omit the patterns content path. Restart the development process after changing Tailwind scanning paths.

Load Tokens Once​

Import the canonical tokens before the Tailwind layers in the application global stylesheet:

@import "@thyris/ui/tokens.css";

@tailwind base;
@tailwind components;
@tailwind utilities;

Importing tokens.css defines semantic variables. The Tailwind configuration creates utilities such as bg-background, text-foreground, and border-border. Both pieces are required.

Create the Application Shell​

Apply semantic page colors at a shared ancestor:

export function AppShell({children}: {children: React.ReactNode}) {
return (
<div className="min-h-screen bg-background text-foreground">
{children}
</div>
)
}

Do not hard-code a white application background. A literal light background breaks dark mode even when individual components use the correct tokens.

Import Components​

import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@thyris/ui"

export function AccountCard() {
return (
<Card>
<CardHeader>
<CardTitle>Account</CardTitle>
<CardDescription>Manage the current account.</CardDescription>
</CardHeader>
<CardContent>
<Button type="button">Save changes</Button>
</CardContent>
</Card>
)
}

Use the root export for normal application code. A documented subpath such as @thyris/ui/button is useful when the host toolchain benefits from a narrow import.

Button can provide presentation to a semantic application link through asChild:

import Link from "next/link"
import {Button} from "@thyris/ui"

export function SettingsLink() {
return (
<Button asChild variant="outline">
<Link href="/settings">Open settings</Link>
</Button>
)
}

Do not replace navigation with a button click handler. Preserving link semantics keeps browser, keyboard, and assistive-technology behavior intact.

Client Boundaries​

Interactive primitives declare their own client boundary. A consuming Next.js component still needs "use client" when it owns state, event handlers, browser APIs, or hooks.

"use client"

import {useState} from "react"
import {Switch} from "@thyris/ui"

export function NotificationSetting() {
const [enabled, setEnabled] = useState(false)

return (
<Switch
checked={enabled}
onCheckedChange={setEnabled}
aria-label="Enable notifications"
/>
)
}

Static composition can remain server-rendered when no client-only props cross the boundary.

Utilities​

Use cn to merge conditional Tailwind classes safely:

import {cn} from "@thyris/ui"

export function Panel({active}: {active: boolean}) {
return (
<section
className={cn(
"rounded-lg border bg-card p-4 text-card-foreground",
active && "ring-2 ring-ring",
)}
/>
)
}

Use useMediaQuery only for client-side behavioral differences. Essential layout and content must remain usable before hydration.

Setup Verification​

  • A Button displays its variant, radius, and focus styles.
  • bg-background and text-foreground resolve in the application shell.
  • Adding dark to the document root changes surfaces and text together.
  • Interactive components use a single React runtime.
  • Production builds scan both application and installed package output.