UI components

Install in an app outside the repository

Pack @werft-ai/ui into a tarball and install it in another React app

This page shows how to use @werft-ai/ui in a React app outside this repository. You build the package in the repository, pack it into a tarball, and npm install that file in the app. The app does not need Tailwind.

The package is not published to a registry yet, so for now the tarball below is the only way to install it. @werft-ai/react (the hooks and session blocks) is not distributed, so apps outside the repository cannot use it.

This repository is source-available under the PolyForm Small Business License 1.0.0, and the package carries the repository's LICENSE. A company with fewer than 100 employees and independent contractors and less than USD 1,000,000 (2019, adjusted for inflation) total revenue in the prior tax year may use it free of charge, counting every organization that controls it, is controlled by it, or is under common control with it; a larger company needs a commercial license. Evaluation and commercial licensing inquiries: jaesoon@94soon.dev

Make the tarball

Run these at the repository root. You need the Bun the repository pins, 1.3.14 (packageManager in the root package.json), and npm.

bun install
bun run --cwd packages/ui build:package
npm pack ./packages/ui/dist

build:package writes the built JavaScript and type declarations, the CSS, a package.json for distribution, and LICENSE into packages/ui/dist. npm pack packs that directory into werft-ai-ui-<version>.tgz in the current directory. The version is the version in packages/ui/package.json.

Install it in an app

The example is a Vite + React + TypeScript app. If you do not have one, create it with npm create vite@latest my-app -- --template react-ts.

@werft-ai/ui takes react and react-dom ^19.3.0 as peer dependencies. If the app's React is older, upgrade it first, then install the tarball you made.

npm install react@^19.3.0 react-dom@^19.3.0
npm install <path to the repository>/werft-ai-ui-0.0.0.tgz

Radix, class-variance-authority, clsx, tailwind-merge, and lucide-react are dependencies of the package and are installed with it.

Import the CSS

Import the prebuilt styles.css once. It holds the tokens, the rules for the kit's elements, and the utility classes the kit uses, so the components render without Tailwind.

src/main.tsx
import "@werft-ai/ui/styles.css";
import { Button, Card } from "@werft-ai/ui";

styles.css does not include Tailwind's preflight (its global reset). It sets one global rule: every element's default border-color is --werft-color-border, so an element the app gives a border-width and border-style but no colour draws in that colour (the border shorthand sets a colour too, so it is not affected). The resets the kit expects (borders, margins, button backgrounds, link underlines, and so on) apply only to the kit's elements and what is inside them, at zero specificity. App markup you put inside a kit component also loses the browser's default margins and underlines. The kit's rules sit in a cascade layer, so the app's CSS outside any layer always wins.

Tailwind v4 apps

To use the kit's utility names (bg-primary, text-control, rounded-control, and others) in the app's own markup, import theme.css after styles.css in the app's CSS. theme.css connects the tokens to Tailwind utilities.

src/index.css
@import "tailwindcss";
@import "@werft-ai/ui/styles.css";
@import "@werft-ai/ui/theme.css";

The classes the kit's components use are already in styles.css, so you do not need to scan node_modules with @source.

Fonts

The package ships no fonts. The font lists live in --werft-font-sans (Geist, then Pretendard Variable) and --werft-font-mono (Geist Mono first), but styles.css applies them only to the kit's werft-* elements. Controls such as buttons and segmented controls inherit the app's font, so if the app sets no font on body (or :root), they render in the browser's default font, usually a serif such as Times. Put one line in the app's CSS:

src/app.css
body { font-family: var(--werft-font-sans); }

With it, text falls back to the system fonts at the end of the list even when the app loads neither Geist nor Pretendard. To show Hangul in Pretendard, the app loads it itself. This repository's web console uses the dynamic subset from the pretendard package.

npm install pretendard
src/main.tsx
import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css";

Theme

Light is the default. Turning on the dark theme works the same as in Apply the theme.

Import paths

PathContents
@werft-ai/uiThe two layers below and all of foundations (cn and others)
@werft-ai/ui/primitivesButton, Card, Dialog, Tabs, Input, Skeleton, and others
@werft-ai/ui/patternsConfirmDialog, EmptyState, ErrorState, PageHeader, Stepper, and others
@werft-ai/ui/styles.cssThe prebuilt CSS. Usually the only one you import
@werft-ai/ui/theme.cssThe token connection for Tailwind v4 apps
@werft-ai/ui/tokens.cssOnly the --werft-* variables

Next steps

On this page