Skip to content
Night Owls.dev
Jump to a page

@nightowlsdev/ui

UI

The 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/ui

Key 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

ui.tsx
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.

ui-example-1.tsx
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).

ui-example-2.tsx
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.

ui-example-3.tsx
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

  • themeThe --owl-* design tokens these components are styled against; ThemeProvider resolves + applies them.
  • reactThe chat UI that mounts INSIDE this chrome, sharing the same tokens.
  • graph-reactAnother surface reading the same --owl-* custom properties.