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.
Use Links as Links
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
Buttondisplays its variant, radius, and focus styles. bg-backgroundandtext-foregroundresolve in the application shell.- Adding
darkto the document root changes surfaces and text together. - Interactive components use a single React runtime.
- Production builds scan both application and installed package output.