npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brijbyte/agentic-ui

v0.0.5

Published

A developer-focused React component library with an aesthetic loosely inspired by native desktop and mobile UI. Built on top of [`@base-ui/react`](https://base-ui.com) for accessible behaviour, styled with CSS modules under `@layer` rules so every compone

Readme

@brijbyte/agentic-ui

A developer-focused React component library with an aesthetic loosely inspired by native desktop and mobile UI. Built on top of @base-ui/react for accessible behaviour, styled with CSS modules under @layer rules so every component style is overridable downstream.

Documentation: agentic-ui.brijbyte.com


Design philosophy

  • Monospace-first UI. All chrome (buttons, labels, inputs, badges) uses a monospace font stack — feels native in developer tools and dashboards.
  • Serif display font for headings. Titles use a serif font stack ("New York" where available) with a graceful fallback chain.
  • Semantic CSS custom properties. Every visual value — colour, spacing, radius, shadow, easing — is a CSS variable. Dark mode and per-app theming are done by re-declaring variables, not by shipping a separate stylesheet.
  • Generic styled parts. Every complex component exports both a high-level wrapper and individual styled primitives. You can swap in a raw @base-ui/react root while keeping the styled children — or the other way around.
  • CSS-free JS output. The built JavaScript never imports CSS. You import the stylesheet yourself, explicitly. This is standards-compliant and works with any bundler.
  • Layered component styles. Component CSS modules live in @layer components. The layer order is theme (lowest) → basecomponentsutilities (highest). Declared once in tokens.css and exposed via @brijbyte/agentic-ui/layer-order — not repeated in every module file. Tokens in @layer theme; resets in @layer base; components beat both; Tailwind utilities beat everything.

Installation

pnpm add @brijbyte/agentic-ui @base-ui/react react react-dom

Peer dependencies: react ^19, react-dom ^19.


CSS setup

Without Tailwind

Option A — full bundle (simplest): one import loads all tokens, resets, and component CSS:

@import "@brijbyte/agentic-ui/styles";

Option B — per-component (smallest bundle): import tokens + reset once, then add one /<name>.css import per component you actually use:

@import "@brijbyte/agentic-ui/tokens";
@import "@brijbyte/agentic-ui/reset";

/* One line per component you use */
@import "@brijbyte/agentic-ui/button.css";
@import "@brijbyte/agentic-ui/dialog.css";
@import "@brijbyte/agentic-ui/toast.css";

Every component has a plain /<name>.css subpath export. The JS and CSS are always separate explicit imports — the JS module never auto-injects CSS.

With Tailwind v4

/* app/globals.css */
@import "tailwindcss";

/* tokens.css includes the layer order declaration */
@import "@brijbyte/agentic-ui/tokens";
@import "@brijbyte/agentic-ui/reset";
@import "@brijbyte/agentic-ui/tailwind-theme";

tokens.css declares @layer theme, base, components, utilities — the same order Tailwind v4 uses — so component styles in @layer components always beat preflight resets in @layer base, and Tailwind utilities in @layer utilities always beat component styles.

After this, every token is available as a Tailwind utility class:

| Token | Tailwind class | CSS output | | ----------------- | -------------------------- | --------------------------------------- | | --color-canvas | bg-canvas | background-color: var(--color-canvas) | | --color-primary | text-primary | color: var(--color-primary) | | --color-line | border-line | border-color: var(--color-line) | | --color-accent | bg-accent, text-accent | references live CSS variable | | --font-mono | font-mono | font-family: var(--font-mono) | | --font-display | font-display | font-family: var(--font-display) | | --radius-md | rounded-md | border-radius: var(--radius-md) | | --shadow-sm | shadow-sm | box-shadow: var(--shadow-sm) |

Because @theme inline references live CSS variables (not baked-in values), dark mode switches automatically — no dark: prefix, no extra configuration.

The tailwind-theme file contains @theme inline {} which is a Tailwind directive, not plain CSS. It must be processed by your Tailwind pipeline (@tailwindcss/vite or @tailwindcss/postcss).

CSS layer order

The cascade order base → components → utilities is declared in tokens.css, so importing tokens is sufficient in most setups.

If you're importing component CSS without tokens — for example, using a pre-built button.css in isolation — import layer-order first:

@import "@brijbyte/agentic-ui/layer-order";
@import "@brijbyte/agentic-ui/button.css";
@layer theme       ← design tokens (CSS custom properties)   ← lowest priority
@layer base        ← element resets + Tailwind preflight      ← beats theme
@layer components  ← component CSS modules                    ← beats base
@layer utilities   ← Tailwind utilities                       ← beats everything

CSS only honours the first time each layer name appears in an ordering statement — subsequent declarations from other files are harmless.

Dark mode

Tokens adapt automatically via prefers-color-scheme. To override programmatically, set data-theme on <html>:

document.documentElement.dataset.theme = "light"; // force light
document.documentElement.dataset.theme = "dark"; // force dark
delete document.documentElement.dataset.theme; // follow OS

Design tokens

All tokens are CSS custom properties defined at :root under @layer base. Override any of them after importing the stylesheet.

Typography

--font-display  "New York", "Iowan Old Style", "Palatino Linotype", Georgia, serif
--font-mono     "Berkeley Mono", "JetBrains Mono", "Fira Code", "SF Mono", "Menlo", ui-monospace
--font-sans     -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial

--font-display is used for h1h6 and prominent page titles. --font-mono is used for all UI chrome — buttons, inputs, labels, badges, body text.

Colour roles (semantic, light + dark)

Token names describe what the colour is, not which CSS property it maps to. Tailwind's utility prefix (bg-, text-, border-) adds the property context — so --color-canvas becomes bg-canvas, --color-primary becomes text-primary, etc.

All tokens live in @layer theme — the lowest-priority layer. @layer base (resets) beats them, @layer components beats base, and @layer utilities beats everything. Any unlayered :root override also beats all layers without needing !important.

Shadows are theme-aware — light mode uses low-opacity black shadows; dark mode uses higher-opacity values so elevation is visible against dark surfaces.

| Token | Tailwind class | Role | | --------------------------------------------------------- | -------------------- | -------------------------------------------- | | --color-canvas | bg-canvas | Page background | | --color-elevated | bg-elevated | Cards, popovers | | --color-sunken | bg-sunken | Code blocks, inset areas | | --color-overlay | bg-overlay | Backdrop-blurred surfaces | | --color-sidebar | bg-sidebar | Sidebar / navigation panels | | --color-surface-1/2/3 | bg-surface-1/2/3 | Layered surface hierarchy | | --color-hover | bg-hover | Hovered interactive surface | | --color-active | bg-active | Pressed interactive surface | | --color-selected | bg-selected | Selected interactive surface | | --color-primary | text-primary | Primary text | | --color-secondary | text-secondary | Secondary text | | --color-tertiary | text-tertiary | Tertiary / hint text | | --color-disabled | text-disabled | Disabled text | | --color-inverse | text-inverse | Text on inverse (dark) backgrounds | | --color-on-accent | text-on-accent | Text on filled accent backgrounds | | --color-code | text-code | Inline code text | | --color-line | border-line | Base border | | --color-line-strong | border-line-strong | Stronger border | | --color-line-subtle | border-line-subtle | Subtle / decorative border | | --color-accent | bg-accent | macOS blue — links, focus rings, tints | | --color-accent-solid | — | Filled button bg — AA contrast vs white | | --color-accent-text | — | Accent text on tinted surfaces — AA contrast | | --color-accent-hover | — | Accent on hover | | --color-accent-pressed | — | Accent on press | | --color-accent-tint | bg-accent-tint | Subtle accent fill (badges, soft buttons) | | --color-accent-tint-hover | — | Tint on hover | | --color-focus-ring | — | Focus ring colour (semi-transparent accent) | | --color-success/warning/error/info-bg/border/text/solid | — | Status colours | | --color-success/warning/error-on-solid | — | Text on filled status buttons (AA contrast) |

Spacing

Full 4 px grid with no gaps. Tokens: --space-px (1px), --space-0-5 (2px), --space-1 (4px), --space-1-5 (6px), --space-2 (8px), --space-2-5 (10px), --space-3 (12px), --space-4 (16px), --space-5 (20px), --space-6 (24px), --space-7 (28px), --space-8 (32px), --space-9 (36px), --space-10 (40px) … --space-24 (96px).

Border radius

--radius-none, --radius-sm (4px), --radius-md (6px), --radius-lg (8px), --radius-xl (10px), --radius-2xl (12px), --radius-full (9999px).

Border width

--border-width-base (1px), --border-width-medium (1.5px), --border-width-thick (2px).

Shadows

Theme-aware — defined inside the light/dark token blocks so they adapt automatically.

--shadow-xs/sm/md/lg/xl, --shadow-popover (dropdown/tooltip), --shadow-focus (two-layer ring: offset gap + coloured ring), --shadow-inset.

--shadow-focus uses a two-layer technique: 0 0 0 2px --color-canvas, 0 0 0 4px --color-focus-ring. The inner ring punches a gap in the page background colour, making the focus ring clearly visible on any background — light or dark.

Animation

/* Durations */
--duration-fast    100ms
--duration-normal  150ms
--duration-slow    200ms
--duration-slower  300ms

/* Easings — use the right one for the job */
--easing-ease-out  cubic-bezier(0, 0, 0.2, 1)        /* entering elements — starts fast  */
--easing-ease-in   cubic-bezier(0.4, 0, 1, 1)        /* exiting elements  — ends fast   */
--easing-standard  cubic-bezier(0.4, 0, 0.2, 1)      /* on-screen movement / morphing   */
--easing-spring    cubic-bezier(0.34, 1.56, 0.64, 1) /* physics-based, slight overshoot */
--easing-linear    linear                             /* constant motion (spinners, progress) */

Z-index scale

--z-dropdown (100), --z-sticky (200), --z-overlay (300), --z-modal (400), --z-popover (500), --z-toast (600), --z-tooltip (700).


Package exports

@brijbyte/agentic-ui                 ← barrel (all components + StylesObjects)

CSS bundles
@brijbyte/agentic-ui/styles          ← full bundle: tokens + reset + all components
@brijbyte/agentic-ui/layer-order     ← layer order declaration only
@brijbyte/agentic-ui/tokens          ← CSS custom properties (includes layer-order)
@brijbyte/agentic-ui/reset           ← element resets
@brijbyte/agentic-ui/tailwind-theme  ← @theme inline block for Tailwind v4

Per-component JS  (also exports <Name>Styles class-name map)
@brijbyte/agentic-ui/accordion       @brijbyte/agentic-ui/menu
@brijbyte/agentic-ui/alert-dialog    @brijbyte/agentic-ui/meter
@brijbyte/agentic-ui/badge           @brijbyte/agentic-ui/number-field
@brijbyte/agentic-ui/button          @brijbyte/agentic-ui/popover
@brijbyte/agentic-ui/card            @brijbyte/agentic-ui/progress
@brijbyte/agentic-ui/checkbox        @brijbyte/agentic-ui/radio
@brijbyte/agentic-ui/collapsible     @brijbyte/agentic-ui/radio-group
@brijbyte/agentic-ui/context-menu    @brijbyte/agentic-ui/select
@brijbyte/agentic-ui/dialog          @brijbyte/agentic-ui/separator
@brijbyte/agentic-ui/drawer          @brijbyte/agentic-ui/slider
@brijbyte/agentic-ui/input           @brijbyte/agentic-ui/switch
@brijbyte/agentic-ui/menu            @brijbyte/agentic-ui/tabs
                                     @brijbyte/agentic-ui/toast
                                     @brijbyte/agentic-ui/tooltip

Per-component CSS  (/<name>.css mirrors each JS subpath)
@brijbyte/agentic-ui/accordion.css    @brijbyte/agentic-ui/menu.css
@brijbyte/agentic-ui/alert-dialog.css @brijbyte/agentic-ui/meter.css
@brijbyte/agentic-ui/badge.css        @brijbyte/agentic-ui/number-field.css
@brijbyte/agentic-ui/button.css       @brijbyte/agentic-ui/popover.css
@brijbyte/agentic-ui/card.css         @brijbyte/agentic-ui/progress.css
@brijbyte/agentic-ui/checkbox.css     @brijbyte/agentic-ui/radio.css
@brijbyte/agentic-ui/collapsible.css  @brijbyte/agentic-ui/radio-group.css
@brijbyte/agentic-ui/context-menu.css @brijbyte/agentic-ui/select.css
@brijbyte/agentic-ui/dialog.css       @brijbyte/agentic-ui/separator.css
@brijbyte/agentic-ui/drawer.css       @brijbyte/agentic-ui/slider.css
@brijbyte/agentic-ui/input.css        @brijbyte/agentic-ui/switch.css
@brijbyte/agentic-ui/menu.css         @brijbyte/agentic-ui/tabs.css
                                      @brijbyte/agentic-ui/toast.css
                                      @brijbyte/agentic-ui/tooltip.css

Components

Each component is available at its own deep-import path for tree-shaking:

import { Button } from "@brijbyte/agentic-ui/button";
import { Dialog, DialogPopup, DialogTitle } from "@brijbyte/agentic-ui/dialog";

Or import everything from the barrel (convenient for prototyping):

import { Button, Dialog, Input } from "@brijbyte/agentic-ui";

Component list

| Import path | High-level wrapper | Styled parts | | --------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | /accordion | Accordion | AccordionItemPart, AccordionHeader, AccordionTrigger, AccordionPanel | | /alert-dialog | AlertDialog | AlertDialogBackdrop, AlertDialogPopup, AlertDialogTitle, AlertDialogDescription | | /badge | Badge | — | | /button | Button | — | | /card | Card, CardHeader, CardBody, CardFooter | — | | /checkbox | Checkbox | CheckboxRoot, CheckboxIndicator | | /collapsible | Collapsible | CollapsibleRoot, CollapsibleTrigger, CollapsiblePanel | | /context-menu | ContextMenu | ContextMenuPopup, ContextMenuItem, ContextMenuSeparator, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuSubmenuTrigger | | /dialog | Dialog | DialogBackdrop, DialogViewport, DialogPopup, DialogTitle, DialogDescription, DialogClose | | /drawer | Drawer | DrawerBackdrop, DrawerViewport, DrawerPopup, DrawerContent, DrawerTitle, DrawerDescription, DrawerClose, DrawerHandleBar, DrawerFooter | | /input | Input | — | | /menu | Menu | MenuPositioner, MenuPopup, MenuItem, MenuSeparator, MenuGroupLabel, MenuArrow, MenuItemShortcut | | /number-field | NumberField | NumberFieldGroup, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, NumberFieldScrubArea, NumberFieldScrubAreaCursor | | /meter | Meter, CircularMeter | MeterTrack, MeterIndicator, MeterLabel, MeterValue | | /popover | Popover | PopoverPositioner, PopoverPopup, PopoverTitle, PopoverDescription, PopoverClose, PopoverArrow, PopoverViewport | | /progress | Progress | ProgressTrack, ProgressIndicator | | /radio | Radio | RadioRoot, RadioIndicator | | /radio-group | RadioGroup | RadioGroupRoot | | /select | Select | SelectTrigger, SelectValue, SelectPositioner, SelectPopup, SelectList, SelectItem, SelectItemText, SelectItemIndicator, SelectGroupContainer, SelectGroupLabel, SelectSeparator | | /separator | Separator | — | | /slider | Slider | SliderControl, SliderTrack, SliderIndicator, SliderThumb, SliderLabel, SliderValue | | /switch | Switch | SwitchRoot, SwitchThumb | | /tabs | Tabs | TabsList, TabsTab, TabsPanel | | /toast | ToastViewport, ToastProvider, useToastManager | ToastRoot, ToastTitle, ToastDescription, ToastClose, ToastViewportPart | | /tooltip | Tooltip | TooltipPositioner, TooltipPopup, TooltipArrow |

Every deep-import path also exports a <ComponentName>Styles object — the CSS module class name map. See Accessing class names.


Usage patterns

High-level wrapper (batteries included)

import "@brijbyte/agentic-ui/styles";
import { Button } from "@brijbyte/agentic-ui/button";
import { Dialog } from "@brijbyte/agentic-ui/dialog";

<Button variant="solid" tone="primary" size="md">Deploy</Button>

<Dialog
  trigger={<Button>Open settings</Button>}
  title="API Settings"
  description="Configure your endpoint."
  footer={<Button size="sm">Save</Button>}
>
  <Input placeholder="https://api.example.com" />
</Dialog>

Composed: raw base-ui root + styled parts

Use a raw @base-ui/react Root for full control over behaviour (controlled state, custom refs, delays), while keeping all the pre-styled child parts:

import * as BaseDialog from "@base-ui/react/dialog";
import { DialogBackdrop, DialogViewport, DialogPopup, DialogTitle, DialogClose } from "@brijbyte/agentic-ui/dialog";

<BaseDialog.Root open={open} onOpenChange={setOpen}>
  <BaseDialog.Trigger render={<button>Open</button>} />
  <BaseDialog.Portal>
    <DialogBackdrop />
    <BaseDialog.Viewport>
      <DialogPopup>
        <DialogTitle>Custom Dialog</DialogTitle>
        <DialogClose aria-label="Close">✕</DialogClose>
        {children}
      </DialogPopup>
    </BaseDialog.Viewport>
  </BaseDialog.Portal>
</BaseDialog.Root>;

The same pattern applies to every component with sub-parts.

Overriding component styles

Every styled part accepts className. Add your own classes — they append after the built-in ones. Because component styles live in @layer components, any @layer utilities rule or unlayered rule you write will win in the cascade.

<Button className="my-custom-button">Click</Button>
/* utility class → beats @layer components */
.my-custom-button {
  border-radius: 0;
}

Overriding tokens

Re-declare any CSS variable after importing the library stylesheet:

@import "@brijbyte/agentic-ui/styles";

:root {
  --font-mono: "Cascadia Code", monospace; /* swap the mono font */
  --color-accent: #7c3aed; /* purple accent instead of blue */
  --radius-md: 2px; /* sharper corners */
}

Accessing class names

Every deep-import path exports a <ComponentName>Styles object — the CSS module class name map. Keys are the local names from source, values are the hashed runtime names. Useful for tests, runtime class lookups, or extending styles:

import { ButtonStyles } from "@brijbyte/agentic-ui/button";

// In a test — assert a class was applied
expect(el.className).toContain(ButtonStyles.root);

// At runtime — read the hashed name
const solidClass = ButtonStyles["variant-solid"]; // → "variant-solid_a1b2c3d4"

Components reference

Button

import { Button } from "@brijbyte/agentic-ui/button";

// variant: solid | soft | outline | ghost
// tone:    primary | secondary | destructive | success | warning
// size:    xs | sm | md | lg
<Button variant="solid" tone="primary" size="md">Deploy</Button>
<Button variant="soft" tone="destructive">Delete</Button>
<Button variant="outline" size="sm" loading>Saving…</Button>
<Button variant="ghost" loading loadingText="Deploying…">Deploy</Button>
<Button variant="soft" nativeButton={false} render={<a href="/docs" />}>Docs</Button>

variant controls visual shape — solid (filled), soft (tinted), outline (bordered), ghost (borderless).

tone controls semantic colour — any tone works with any variant:

| Tone | Use case | | ------------- | ------------------------------- | | primary | Default. Primary action | | secondary | Neutral / less prominent action | | destructive | Irreversible / dangerous action | | success | Confirm / complete action | | warning | Caution required |

All tone/variant combinations meet WCAG AA contrast (4.5:1 for text, 3:1 for borders). Each tone exposes two colour roles internally — a solid fill colour (--btn-color) and a separate text colour (--btn-text-color) optimised for legibility on tinted surfaces — so soft, outline, and ghost variants are always readable regardless of mode.

loading disables the button and shows a centred spinner. The button width stays stable — children are hidden with visibility: hidden so they still contribute to layout.

loadingText overrides the stable-width behaviour. When set, children swap to this text during loading (no spinner). Use when the loading state needs to communicate progress via text.

render passes through to @base-ui/react Button's render prop — use it to render an <a> or any custom element while keeping button behaviour. Set nativeButton={false} when rendering a non-<button> element.

Badge

import { Badge } from "@brijbyte/agentic-ui/badge";

// variants: default | solid | soft | success | warning | error | info
<Badge variant="success" dot>Active</Badge>
<Badge variant="error">Failed</Badge>

dot shows a small coloured dot before the label text.

Card

import { Card, CardHeader, CardBody, CardFooter } from "@brijbyte/agentic-ui/card";

<Card>
  <CardHeader heading="API Key" description="Used for authentication" />
  <CardBody>sk-1234…</CardBody>
  <CardFooter>
    <Button variant="ghost" size="sm">Revoke</Button>
    <Button size="sm">Copy</Button>
  </CardFooter>
</Card>

<Card elevated interactive onClick={() => {}}>
  <CardBody>Clickable elevated card</CardBody>
</Card>

elevated adds a raised shadow for layered surfaces. interactive adds hover/active states for clickable cards. CardHeader accepts heading and description props. All sub-components are plain <div> wrappers — no base-ui dependency.

Input

import { Input } from "@brijbyte/agentic-ui/input";

// sizes: sm | md | lg
<Input size="md" placeholder="Search…" leftAdornment={<SearchIcon />} />
<Input invalid defaultValue="bad-value" />

size overrides the native HTML size attribute — it maps to height and font-size design tokens.

Checkbox

import { Checkbox, CheckboxRoot, CheckboxIndicator } from "@brijbyte/agentic-ui/checkbox";

// High-level
<Checkbox checked={val} onCheckedChange={(v) => setVal(v as boolean)}>
  Accept terms
</Checkbox>

// Composed — custom icon
<label htmlFor="cb">
  <CheckboxRoot id="cb">
    <CheckboxIndicator><StarIcon /></CheckboxIndicator>
  </CheckboxRoot>
  Favourite
</label>

Switch

import { Switch } from "@brijbyte/agentic-ui/switch";

<Switch checked={enabled} onCheckedChange={(v) => setEnabled(v as boolean)}>
  Enable notifications
</Switch>;

Select

import { Select } from "@brijbyte/agentic-ui/select";

<Select
  placeholder="Choose a model…"
  options={[
    { value: "claude-3-5-sonnet", label: "Claude 3.5 Sonnet" },
    { value: "gpt-4o", label: "GPT-4o", disabled: true },
  ]}
  onValueChange={(v) => console.log(v)}
/>

// With groups
<Select
  groups={[
    { label: "Anthropic", options: [{ value: "claude", label: "Claude" }] },
    { label: "OpenAI",    options: [{ value: "gpt",    label: "GPT"    }] },
  ]}
/>

The trigger always displays the selected option's label (which can be any ReactNode — text, icons, etc.) rather than the raw value string. Label lookup is memoised.

Dialog

import { Dialog } from "@brijbyte/agentic-ui/dialog";

<Dialog
  trigger={<Button>Open</Button>}
  title="Confirm deletion"
  description="This cannot be undone."
  footer={
    <>
      <Button variant="ghost" size="sm">
        Cancel
      </Button>
      <Button variant="solid" tone="destructive" size="sm">
        Delete
      </Button>
    </>
  }
>
  Optional body content
</Dialog>;

Drawer

import { Drawer, DrawerClose } from "@brijbyte/agentic-ui/drawer";

// sides: left | right (default) | top | bottom
<Drawer
  side="right"
  trigger={<Button>Open</Button>}
  title="Settings"
  footer={
    <Button size="sm" render={<DrawerClose />}>
      Done
    </Button>
  }
>
  Content goes here
</Drawer>;

Tooltip

import { Tooltip } from "@brijbyte/agentic-ui/tooltip";

// side: top | bottom | left | right  (default: "top")
<Tooltip content="Keyboard shortcut: ⌘K" side="bottom">
  <Button variant="ghost">Open palette</Button>
</Tooltip>;

Tooltips spring open from the trigger's origin — Base UI sets --transform-origin to the trigger side so the scale animation radiates outward from the correct edge. Enter uses --easing-spring (overshoot + settle). Exit is a fast ease-in fade with no spring. When the user moves quickly between adjacent tooltips, subsequent ones appear instantly via data-instant.

Tabs

import { Tabs } from "@brijbyte/agentic-ui/tabs";

<Tabs
  items={[
    { value: "overview", label: "Overview", content: <OverviewPanel /> },
    { value: "api", label: "API", content: <ApiPanel /> },
    { value: "old", label: "Legacy", content: null, disabled: true },
  ]}
/>;

The active tab is indicated by a 3px accent-coloured underline and full-brightness text vs dimmed inactive tabs. Tab panels fade in on switch.

Accordion

import { Accordion } from "@brijbyte/agentic-ui/accordion";

<Accordion
  multiple={false}
  items={[
    { value: "q1", header: "What is it?", content: "…" },
    { value: "q2", header: "How does it work?", content: "…" },
  ]}
/>;

Collapsible

import { Collapsible } from "@brijbyte/agentic-ui/collapsible";

<Collapsible trigger="Recovery keys" defaultOpen>
  <div>alien-bean-pasta</div>
  <div>wild-irish-burrito</div>
</Collapsible>;

When open, the trigger's bottom corners square off and connect flush with the content panel. When closing, the corners only round back after the panel has fully collapsed — no visible seam at any point during the animation.

Popover

import { Popover } from "@brijbyte/agentic-ui/popover";

// side: top | bottom (default) | left | right
// align: start | center (default) | end
<Popover
  trigger={<Button variant="outline" size="sm">Details</Button>}
  title="Deployment info"
  description="Last deployed 3 minutes ago from main."
  side="bottom"
  align="start"
/>

// With body content and a close button
<Popover
  trigger={<Button>Settings</Button>}
  title="API Settings"
  dismissible
>
  <Input placeholder="API key" style={{ marginTop: "var(--space-3)" }} />
</Popover>

trigger is rendered as-is and receives the open/close handler from base-ui.

dismissible renders a close (×) button in the top-right corner. Omit it for simple informational popovers that dismiss on outside click.

side / align control positioning. The popup auto-repositions to avoid viewport overflow — it will flip sides when there isn't enough space.

Shared trigger (one popover, many triggers)

Use Popover.createHandle from base-ui to connect multiple triggers to a single popup instance. Each trigger passes its own payload; the Root receives it via a render-function child. Switching triggers while the popup is open animates the content transition with PopoverViewport:

import { Popover as BasePopover } from "@base-ui/react/popover";
import { PopoverPopup, PopoverTitle, PopoverDescription, PopoverArrow, PopoverViewport } from "@brijbyte/agentic-ui/popover";

interface Item {
  id: string;
  name: string;
  detail: string;
}

// Create once outside the component — typed with the payload shape
const handle = BasePopover.createHandle<Item>();

function MyList({ items }: { items: Item[] }) {
  return (
    <>
      {/* Single Root — content driven by whichever trigger is active */}
      <BasePopover.Root handle={handle}>
        {({ payload }) =>
          payload ? (
            <BasePopover.Portal>
              <BasePopover.Positioner side="right" sideOffset={8}>
                <PopoverPopup>
                  <PopoverArrow />
                  {/* Viewport animates between triggers */}
                  <PopoverViewport>
                    <PopoverTitle>{payload.name}</PopoverTitle>
                    <PopoverDescription>{payload.detail}</PopoverDescription>
                  </PopoverViewport>
                </PopoverPopup>
              </BasePopover.Positioner>
            </BasePopover.Portal>
          ) : null
        }
      </BasePopover.Root>

      {/* Each trigger passes its item as the payload */}
      {items.map((item) => (
        <BasePopover.Trigger
          key={item.id}
          handle={handle}
          payload={item}
          render={
            <Button variant="ghost" size="xs">
              Info
            </Button>
          }
        />
      ))}
    </>
  );
}

PopoverViewport detects the direction between the old and new trigger and slides content in/out accordingly — down when moving to a lower trigger, up when moving higher, left/right for horizontal layouts.

Menu

import { Menu } from "@brijbyte/agentic-ui/menu";
import type { MenuEntry } from "@brijbyte/agentic-ui/menu";

const items: MenuEntry[] = [
  { label: "Edit", shortcut: "⌘E", onSelect: () => {} },
  { label: "Rename", shortcut: "⌘R", onSelect: () => {} },
  { type: "separator" },
  { label: "Delete", onSelect: () => {}, disabled: true },
];

<Menu
  trigger={
    <Button variant="outline" size="sm">
      Actions ▾
    </Button>
  }
  items={items}
/>;

Supports type: "group" entries with an optional label for grouped sections. The popup uses backdrop blur and highlights use inset rounded corners — matching native context menu aesthetics.

Meter

import { Meter, CircularMeter } from "@brijbyte/agentic-ui/meter";

// Linear — size: sm | md (default) | lg
<Meter value={68} label="Memory" showValue />

// With thresholds — optimum: "high" (default) | "low" | "mid"
// optimum="high" → high=green, mid=amber, low=red  (battery, signal strength)
// optimum="low"  → low=green, mid=amber, high=red  (CPU load, disk usage)
// optimum="mid"  → mid=green, extremes=amber        (temperature)
<Meter value={82} low={20} high={60} optimum="high" label="Battery" showValue showTicks />

// Intl formatting
<Meter value={0.72} min={0} max={1} format={{ style: "percent" }} label="Disk" showValue />

// Circular gauge
<CircularMeter value={68} label="CPU" showValue />
<CircularMeter value={12} low={20} high={60} optimum="high" label="Battery" showValue size="lg" />

low / high / optimum implement the HTML <meter> spec colouring algorithm — the fill turns green, amber, or red based on which zone the value is in and which zone is declared optimal.

showTicks renders 1px dividers inside the track at the low and high threshold positions (linear only).

format is passed directly to Intl.NumberFormat via base-ui — use it for percent, currency, or any locale-aware format.

CircularMeter accepts the same props as Meter (except showTicks) and renders an SVG ring gauge. Import from the same path: @brijbyte/agentic-ui/meter.

Radio + RadioGroup

import { Radio, RadioRoot, RadioIndicator } from "@brijbyte/agentic-ui/radio";
import { RadioGroup, RadioGroupRoot } from "@brijbyte/agentic-ui/radio-group";

// High-level group with options array
<RadioGroup
  label="Deployment target"
  defaultValue="production"
  options={[
    { value: "production", label: "Production" },
    { value: "staging",    label: "Staging" },
    { value: "local",      label: "Local", disabled: true },
  ]}
/>

// Controlled
<RadioGroup
  value={target}
  onValueChange={(v) => setTarget(v)}
  options={options}
/>

// Composed — RadioGroupRoot + individual Radio items
<RadioGroupRoot defaultValue="pro" name="plan">
  <Radio value="hobby">Hobby</Radio>
  <Radio value="pro">Pro</Radio>
  <Radio value="enterprise">Enterprise</Radio>
</RadioGroupRoot>

// Fully composed — raw base-ui RadioGroup + styled parts
<RadioGroup defaultValue="ssd" aria-label="Storage">
  <label>
    <RadioRoot value="ssd"><RadioIndicator /></RadioRoot>
    SSD
  </label>
</RadioGroup>

Radio and RadioGroup follow the same import split as base-ui — Radio is the single item, RadioGroup is the group context. Both CSS files must be imported when using both:

import "@brijbyte/agentic-ui/radio.css"; // item styles
import "@brijbyte/agentic-ui/radio-group.css"; // group layout styles

NumberField

import { NumberField } from "@brijbyte/agentic-ui/number-field";

<NumberField label="Quantity" defaultValue={1} min={0} max={100} />
<NumberField label="Price" defaultValue={9.99} step={0.01} format={{ style: "currency", currency: "USD" }} />

Click-and-drag on the label (scrub area) to change the value. Supports min, max, step, locale-aware format, and allowWheelScrub. The input and step buttons use a thin inset accent ring on focus rather than the standard offset focus ring.

Separator

import { Separator } from "@brijbyte/agentic-ui/separator";

<Separator />
<Separator orientation="vertical" style={{ height: "1rem" }} />

Slider

import { Slider } from "@brijbyte/agentic-ui/slider";

// Single thumb with label and value display
<Slider label="Volume" defaultValue={50} showValue />

// Range slider — pass an array for two thumbs
<Slider label="Price range" defaultValue={[20, 80]} showValue />

// Steps with Intl formatting
<Slider label="Opacity" defaultValue={100} min={0} max={100} step={10}
  format={{ style: "percent", maximumFractionDigits: 0 }} showValue />

// Disabled
<Slider label="Locked" defaultValue={42} disabled />

defaultValue accepts a single number or an array of two numbers for a range slider. step controls the increment; largeStep (default 10) is used for Page Up/Down. format passes Intl.NumberFormatOptions to the value display. onValueChange fires on every drag; onValueCommitted fires once when the user releases.

Progress

import { Progress } from "@brijbyte/agentic-ui/progress";

// status: default | success | warning | error
// size:   sm | md | lg
<Progress value={72} max={100} label="Uploading" showValue status="success" />
<Progress value={null} label="Loading…" />  {/* indeterminate — linear animation */}

Toast

import { ToastProvider, ToastViewport, useToastManager } from "@brijbyte/agentic-ui/toast";

// 1. Wrap your app
<ToastProvider variant="list">
  <App />
  <ToastViewport variant="list" />
</ToastProvider>;

// 2. Trigger from anywhere
function MyComponent() {
  const toast = useToastManager();
  toast.add({
    title: "Deployed",
    description: "Your app is live.",
    type: "success", // default | success | warning | error | info
  });
}

variant="stacked" — Sonner-style stacked toasts. Collapsed: toasts fan behind each other. Hover/focus expands the stack into a list. Gaps between toasts are covered by a pseudo-element so the stack doesn't collapse mid-hover.

<ToastProvider variant="stacked">
  <App />
  <ToastViewport variant="stacked" />
</ToastProvider>

limit — maximum toasts shown simultaneously. Defaults to 3 (list) / 5 (stacked). Hard-capped at 5 (list) and 10 (stacked) to prevent viewport overflow.

Each toast type gets a subtle color-mix tinted background so variants are distinguishable without relying solely on the icon.

AlertDialog

import { AlertDialog } from "@brijbyte/agentic-ui/alert-dialog";

// 2 actions → side-by-side. Cancel (outline) + confirm (solid).
<AlertDialog
  trigger={<Button variant="outline">Discard draft</Button>}
  title="Discard draft?"
  description="You can't undo this action."
  actions={[
    { label: "Cancel" },
    { label: "Discard", primary: true, destructive: true },
  ]}
/>

// 3+ actions → all right-aligned in order.
<AlertDialog
  title='Save "Untitled" before closing?'
  actions={[
    { label: "Don't Save", destructive: true },
    { label: "Cancel" },
    { label: "Save", primary: true },
  ]}
/>

actions array — each entry: label, onAction, primary (solid variant), destructive (destructive tone). Actions are rendered right-aligned in the order given. primary: true → solid button. destructive: true → destructive tone.

Optional icon prop renders content above the title (e.g. an app icon or warning symbol).

ContextMenu

import { ContextMenu } from "@brijbyte/agentic-ui/context-menu";

<ContextMenu
  items={[
    { label: "Cut", shortcut: "⌘X" },
    { label: "Copy", shortcut: "⌘C" },
    { label: "Paste", shortcut: "⌘V", disabled: true },
    { type: "separator" },
    { label: "Select All", shortcut: "⌘A" },
  ]}
>
  <div>Right click me</div>
</ContextMenu>;

Activated by right-click or long press on the child element. Supports items with label, shortcut, icon, onSelect, and disabled. Use type: "separator" for dividers and type: "group" with nested items for logical sections.

For submenus, use the composed API with ContextMenu.SubmenuRoot + ContextMenuSubmenuTrigger:

import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
import { ContextMenuPopup, ContextMenuItem, ContextMenuSubmenuTrigger } from "@brijbyte/agentic-ui/context-menu";

<BaseContextMenu.SubmenuRoot>
  <ContextMenuSubmenuTrigger>Open With</ContextMenuSubmenuTrigger>
  <BaseContextMenu.Portal>
    <BaseContextMenu.Positioner alignOffset={-4} sideOffset={-4}>
      <ContextMenuPopup>
        <ContextMenuItem>VS Code</ContextMenuItem>
        <ContextMenuItem>Zed</ContextMenuItem>
      </ContextMenuPopup>
    </BaseContextMenu.Positioner>
  </BaseContextMenu.Portal>
</BaseContextMenu.SubmenuRoot>;

Build system

Built with tsdown (powered by Rolldown + Lightning CSS).

pnpm build   # tsdown → dist/
pnpm dev     # tsdown --watch

Output

dist/
  index.js                 ← ESM barrel, no CSS imports
  index.d.ts               ← Type declarations
  index.css                ← All CSS: tokens + reset + every component
  layer-order.css          ← Layer order declaration only
  tokens.css               ← Tokens in @layer theme (includes layer-order)
  reset.css                ← Element resets in @layer base
  tailwind-theme.css       ← @theme inline block for Tailwind v4
  button/index.js          ← Per-component ESM (no CSS imports)
  button/index.d.ts
  button/button.css        ← Per-component plain CSS
  …                        ← All 23 components follow the same pattern

Why no CSS in JS?

@tsdown/css with inject: false extracts all CSS to separate files and writes zero import '…css' statements into JS. Class name mappings from CSS modules are inlined into JS chunks as plain objects — the JS is self-contained, the CSS is an explicit dependency you control.


Tooling

| Tool | Purpose | | ------------------------------------- | ------------------------------------------ | | tsdown | Library bundler (Rolldown + Lightning CSS) | | @typescript/native-preview (tsgo) | Fast TypeScript type checking | | oxlint | ESLint-compatible linter | | oxfmt | Opinionated formatter |

pnpm typecheck   # tsgo --noEmit across all packages
pnpm lint        # oxlint packages/agentic-ui/src apps/docs/src
pnpm format      # oxfmt packages/agentic-ui/src apps/docs/src