Get Started

Installation

View as markdown

The library needs React 19 and Tailwind CSS v4. The tokens are a Tailwind theme. Utilities you already write resolve to the same values the components use.

Install the package

bun add @oscarkalid/ui

Add the stylesheet

Import it once at the top of your global stylesheet. It brings in Tailwind and the tokens. You do not need a separate @import "tailwindcss" line or a Tailwind config file.

CSS

/* app/globals.css */
@import "@oscarkalid/ui/styles.css";
/* Tailwind scans the library for class names on its own. This points it at
yours as well, relative to this file. */
@source "../app";

The @source line is relative to the file it sits in and tells Tailwind where your class names live. The library already covers its own files.

Framework setup

The package ships TypeScript source. Whatever compiles your app has to compile the library too. In Next.js that is one line. Vite and other bundlers that read source need nothing.

next.config.ts

import type { NextConfig } from "next";
const config: NextConfig = {
transpilePackages: ["@oscarkalid/ui"],
};
export default config;

Fonts

Optional. The type tokens point at --font-geist-sans and --font-geist-mono and fall back to the system stack when those are not set.

app/layout.tsx

import { Geist, Geist_Mono } from "next/font/google";
const sans = Geist({ subsets: ["latin"], variable: "--font-geist-sans" });
const mono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono" });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${sans.variable} ${mono.variable}`}>
<body>{children}</body>
</html>
);
}

Themes

Dark is the default. Light is the override.

TypeScript

// Dark needs nothing. Light is the attribute.
<html data-theme="light">
// Back to dark: take it off again.
document.documentElement.removeAttribute("data-theme");

Check it worked

app/page.tsx

import { Button } from "@oscarkalid/ui";
export default function Page() {
return <Button variant="primary">It works</Button>;
}

A button with no colour means the stylesheet has not been picked up. An app styled everywhere except inside the library means the @source line points somewhere your files are not.

Copying a file instead

Every component is a single file. It needs itself, the token sheet and the class merging helper.

Files

packages/ui/src/components/Button.tsx # the component
packages/ui/src/styles/tokens.css # colour, type, motion
packages/ui/src/lib/cn.ts # class merging

Last updated: 12 August 2026