@duly/ui
React UI components for the Studio design system — accessible, themeable, RSC-ready. Ships a precompiled, self-contained dist/styles.css that includes utility classes and theme variable mappings but no CSS preflight, so it is safe to drop into any existing app.
Install
pnpm add @duly/ui @duly/tokens
Minimal setup (works in any app — Tailwind optional)
1. Import the stylesheet once (e.g. in your app entry or root layout):
import "@duly/ui/styles.css";
2. Wrap your UI in a theme root:
// cockpit is the :root default, so data-theme is only required
// when you want to switch to a different theme (e.g. "test").
<div data-theme="cockpit">
{/* your app */}
</div>
cockpitis applied to:rootby default, sodata-theme="cockpit"is technically optional for the default theme. Usedata-themeexplicitly to switch to another theme or to scope a theme to a sub-tree.
3. Use components:
import { TraceLog } from "@duly/ui";
export function AgentPanel() {
return (
<TraceLog.Root density="comfortable" streaming>
<TraceLog.Header title="Agent run" hint="job-42" />
<TraceLog.Body maxHeight={400}>
<TraceLog.Row tone="ok" agent="planner" step="1 / 5" timestamp="12:01:00">
Plan generated — 5 steps queued.
</TraceLog.Row>
<TraceLog.Row tone="warn" agent="executor" step="2 / 5">
Rate limit hit — retrying in 2 s.
<TraceLog.Detail label="error detail">
429 Too Many Requests from provider X.
</TraceLog.Detail>
</TraceLog.Row>
<TraceLog.Code>tool_call("search", {"q": "oklch contrast"})</TraceLog.Code>
</TraceLog.Body>
</TraceLog.Root>
);
}
Already using Tailwind v4?
Instead of importing dist/styles.css, configure your own Tailwind build to include the Duly tokens and scan the compiled output:
/* your app's main CSS entry */
@import "tailwindcss";
@import "@duly/tokens/theme.css"; /* mapping layer: --color-<token> utilities */
@import "@duly/tokens/theme-cockpit.css"; /* value layer: cockpit theme at :root (default) */
@import "@duly/tokens/theme-violet.css"; /* optional: violet theme under [data-theme="violet"] */
@source "../node_modules/@duly/ui/dist/**/*.js"; /* scan compiled output for utility classes */
theme.cssalone is not enough — it only maps utility names to CSS variables; it does not define the variable values. Both the mapping layer (theme.css) and a value layer (theme-cockpit.cssand/ortheme-violet.css) are required, otherwise color utilities resolve to undefined variables and components render colorless.
Then skip the import "@duly/ui/styles.css" line — your Tailwind build covers it.
Optional CSS reset
To apply a lightweight, opinionated CSS reset (box-sizing, margin/padding normalization):
import "@duly/ui/reset.css";
Import this before styles.css.
TraceLog API
TraceLog.Root
| Prop | Type | Default | Description |
|---|---|---|---|
density |
"compact" | "comfortable" |
"comfortable" |
Row padding and font size |
streaming |
boolean |
false |
Sets aria-live="polite" on Body when true; "off" when false |
TraceLog.Header
| Prop | Type | Description |
|---|---|---|
title |
string |
Required. Bold uppercase label |
hint |
string |
Optional. Monospace metadata shown right-aligned |
TraceLog.Body
| Prop | Type | Description |
|---|---|---|
maxHeight |
number |
Optional. Caps scroll area height in px; enables Radix ScrollArea |
TraceLog.Row
| Prop | Type | Default | Description |
|---|---|---|---|
tone |
"ok" | "review" | "warn" | "block" | "info" |
"info" |
Status tone — drives icon + text color |
agent |
string |
— | Required. Agent or subsystem name |
step |
string |
— | Optional. Step label or counter |
timestamp |
string |
— | Optional. Timestamp string (shown instead of step when both present) |
TraceLog.Code
Inline monospace snippet. No props beyond children.
TraceLog.Detail
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
"detalle" |
Accessible label for the collapsible trigger |
TraceLog.Empty
Centered placeholder text. No props beyond children.
TraceLog.Truncated
| Prop | Type | Description |
|---|---|---|
onShowAll |
() => void |
Optional. Click handler for the "show all" button |
Accessibility notes
- Status is never color-only. Every
TraceLog.Rowrenders a tone-specific icon alongside a visually hidden label (<span className="sr-only">), satisfying WCAG 1.4.1. aria-liveis context-driven. Whenstreaming={false}(the default), the Body setsaria-live="off"to suppress re-announcement of static log content on re-render. Setstreaming={true}for live agent runs.