@nightowlsdev/ui
UIThe shared app-chrome kit for Night Owls surfaces: full-page layout (Container/Section), site chrome (NavBar/Footer), primitives (Button/Card/Badge), and a light/dark ThemeProvider.
What it does
@nightowlsdev/ui is the shared React component library for the chrome AROUND a Night Owls swarm (not the chat UI itself, that is @nightowlsdev/react). It has grown past a primitives kit into a full-page layout + app-chrome library: Container and Section for page structure, NavBar and Footer for site chrome, the Button / Card / Badge primitives, a light/dark/system ThemeProvider with useTheme + a ThemeToggle, and a cn class-merge helper (clsx + tailwind-merge). It is styled against the @nightowlsdev/theme tokens (the --owl-* CSS variables) and ships its own precompiled ./styles.css, so a host imports it with no Tailwind build of its own. It powers the marketing site and the getnightowls app shell. Published at version 0.1.0.
Install
pnpm add @nightowlsdev/uiKey exports
- Button (ButtonProps, ButtonVariant, ButtonSize)
- Card
- Badge (BadgeProps, BadgeTone)
- Container (ContainerProps, ContainerSize)
- Section (SectionProps, SectionAlign)
- NavBar (NavBarProps)
- Footer (FooterProps)
- ThemeProvider / useTheme (ThemeProviderProps, ThemeModeSetting)
- ThemeToggle
- cn
- ./styles.css (precompiled)
Usage
import { ThemeProvider, Button, Card, Badge } from "@nightowlsdev/ui";
import "@nightowlsdev/ui/styles.css";
export function Chrome() {
return (
<ThemeProvider defaultMode="light">
<Card>
<Badge tone="accent">New</Badge>
<Button variant="primary" size="md">Start free</Button>
</Card>
</ThemeProvider>
);
}What it provides
ui is the shared React component kit for the chrome AROUND a Night Owls swarm — not the chat itself (that is @nightowlsdev/react). It gives you full-page layout (Container, Section), site chrome (NavBar, Footer), the Button / Card / Badge primitives, a light/dark/system ThemeProvider with useTheme and a ThemeToggle, and a cn class-merge helper. It is styled against the @nightowlsdev/theme --owl-* tokens and ships its own precompiled styles.css, so a host imports it with no Tailwind build of its own.
When to use it
- You are building the pages/chrome around a swarm — a marketing site, an app shell, a settings surface — and want themed layout + primitives without wiring Tailwind.
- You want a drop-in light/dark/system ThemeProvider + ThemeToggle bound to the same --owl-* tokens the chat uses, so chrome and chat share one theme.
- You want a small, opinionated set of building blocks (NavBar/Footer/Section/Container + Button/Card/Badge) rather than a full design system.
When not to
- You want the chat / Room UI itself — that is @nightowlsdev/react, not this.
- You already have your own component library / design system — import the @nightowlsdev/theme tokens directly and style your own components against them.
- You need only design tokens, no components — reach for @nightowlsdev/theme.
Alternatives
- @nightowlsdev/reactYou need the swarm chat surface — messages, delegation, HITL — not the surrounding chrome.
- @nightowlsdev/theme + your own componentsYou want the --owl-* token contract but your own primitives, so the look matches without adopting these components.
- shadcn/ui, Radix, MUI, …You need a comprehensive component library (forms, tables, menus, dialogs). ui is a small chrome kit, deliberately not that.
Strengths
- Precompiled styles.css — no Tailwind build in the host; import one stylesheet and the --owl-* utilities are present.
- Same token contract as the chat, so the chrome and the swarm UI theme together from one source.
- SSR-safe ThemeProvider: the server snapshot pins light to avoid hydration mismatch, then hydrates the persisted choice; 'system' follows the OS live via matchMedia, and localStorage access is guarded for private-browsing/sandboxed contexts.
- Tiny, legible surface: layout (Container/Section), chrome (NavBar/Footer), primitives (Button/Card/Badge), plus cn (clsx + tailwind-merge).
Limits & trade-offs
- A small, opinionated primitive set — not a full design system. No forms, tables, menus, or dialogs; bring those yourself.
- The precompiled styles.css import is mandatory: without it the --owl-* utilities and rounded-owl token are absent and nothing renders correctly.
- Published at 0.1.0 — the youngest, least-battle-tested of the UI family; the API may still move.
- Styling is Tailwind-utility-based internally: deep restyling means overriding the theme tokens or passing className, not a rich variant API (Button has variant/size; Badge has tone).
How it works
ThemeProvider resolves a theme (a built-in name or a Theme object, defaulting to the neutral ink) and a mode (light/dark/system) into the --owl-* CSS vars via @nightowlsdev/theme's resolveThemeInput, then applies them plus a data-theme attribute on a root <div>. It persists the user's light/dark/system choice in localStorage (key nightowls-theme-mode) and follows the OS for 'system' via matchMedia, using useSyncExternalStore so the server and first client render agree. The layout/chrome/primitive components are plain Tailwind-utility React components reading those tokens (bg-owl-accent, rounded-owl, text-owl-muted…); cn merges class names. The shipped styles.css carries the compiled utilities so the host needs no Tailwind.
Examples
App shell: provider, nav, sections
Import the stylesheet once, wrap the tree in ThemeProvider, compose the chrome. NavBar takes brand/links/actions slots.
import { ThemeProvider, NavBar, Section, Container, Button, ThemeToggle } from "@nightowlsdev/ui";
import "@nightowlsdev/ui/styles.css";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider defaultMode="dark">
<NavBar brand={<span>Night Owls</span>} actions={<ThemeToggle />} />
<Section eyebrow="Platform" title="Build smarter agents" align="center">
<Container size="lg">{children}</Container>
</Section>
</ThemeProvider>
);
}Primitives: card, badge, button
Button has variant (primary/secondary/ghost) + size (sm/md/lg); Badge has tone (neutral/accent/success/error).
import { Card, Badge, Button } from "@nightowlsdev/ui";
export function Callout() {
return (
<Card>
<Badge tone="accent">New</Badge>
<Button variant="primary" size="md">Start free</Button>
</Card>
);
}Read and set the theme mode
useTheme exposes the setting (mode), the concrete applied mode (resolvedMode), and setMode. Must be inside a ThemeProvider.
import { useTheme } from "@nightowlsdev/ui";
export function ModeButton() {
const { resolvedMode, setMode } = useTheme();
return (
<button onClick={() => setMode(resolvedMode === "dark" ? "light" : "dark")}>
{resolvedMode === "dark" ? "Go light" : "Go dark"}
</button>
);
}Doing the parts it doesn't support
- The swarm chat surfaceNot here — mount @nightowlsdev/react's <SwarmChat> / <SwarmFab>. ui is only the chrome around it; both read the same --owl-* tokens so they theme together.
- A component the kit doesn't ship (forms, tables, menus)Build your own, styled against the --owl-* tokens (import @nightowlsdev/theme, or reuse the same utility classes) so it matches, and use the exported cn helper for class merging.
- Deeply restyle a primitiveThere is no per-component variant registry beyond Button's variant/size and Badge's tone. Recolor by overriding the theme tokens (@nightowlsdev/theme extendTheme) or pass a className — cn + tailwind-merge lets your class win the conflict.
Related
- theme — The --owl-* design tokens these components are styled against; ThemeProvider resolves + applies them.
- react — The chat UI that mounts INSIDE this chrome, sharing the same tokens.
- graph-react — Another surface reading the same --owl-* custom properties.