@danxbot/ui
v3.4.3
Published
Danxbot — a domain-agnostic React design system with motion as a first-class primitive.
Maintainers
Readme
@danxbot/ui
A React design system with motion as a foundation, not a finish pass.
npm install @danxbot/uiimport { Button, Chat, ThemeMenu } from "@danxbot/ui";/* your app's Tailwind entry — see Setup, all three lines are required */
@import "tailwindcss";
@import "@danxbot/ui/theme.css";
@import "@danxbot/ui/styles.css";Requires React 19 and Tailwind CSS 4 in the consuming build — the package ships no compiled utilities, on purpose. MIT licensed.
What is in it
102 components across forms, overlays, structure, data, layout and chat.
Forms — Field, FieldSet, Input, Textarea, Checkbox, Switch, Radio, RadioCard, Select, Combobox, MultiCombobox, Slider, NumberInput, OTPInput, FileUpload, DatePicker, Toggle, ToggleGroup, Toolbar
Overlays — Dialog, AlertDialog, Popover, Tooltip, Toast, Menu, CommandPalette
Structure — Card, Tabs, Accordion, Breadcrumb, Pagination, AppShell, Separator, ScrollArea, Collapsible, PanelGroup
Display — Avatar, Progress, Meter, ProgressRing, Stat, Sparkline, Timeline, Tree, EmptyState, Badge, Icon, Spinner, Skeleton, LoadingOverlay
Data — DataTable
Chat — Chat, Message, MessageScroller, Composer, Markdown, CodeBlock, ToolCall, Attachment, ModelSelector, EffortControl, ContextMeter
The parts worth knowing about
Motion is a layer, not a decoration
Four spring curves are baked as CSS linear() easings. A spring curve encodes
its damping ratio — its shape — and nothing else, so duration stays a free
parameter and one curve is correct at any speed. There is no JavaScript
animation runtime; the equivalent costs 35–40 KB to do what these do for free.
Reduced motion reduces. The usual animation-duration: 0.01ms !important
sweep is wrong twice over: the spec asks for motion removed, reduced or
replaced, and !important makes the decision unoverridable. Instead
--motion-scale halves durations and --motion-travel zeroes distances, so
components inherit the preference without knowing it exists.
Both themes come from one contract
Colour is a three-tier OKLCH system — primitives, semantic tokens, component tokens — and both themes are written against the same semantic names. Dark accents are hand-tuned at roughly 20% reduced chroma, because a large saturated fill on a dark surface reads as glare rather than emphasis.
Accessibility is a floor you can opt into
Contrast compliance is a switch (data-contrast="aa"), not a permanent state.
Forcing AA everywhere flattens hierarchy: de-emphasised text stops being
de-emphasised. The default is the palette as drawn; the AA overlay is one
attribute away and honours prefers-contrast: more on first load.
Size is a multiplier, not a breakpoint
data-ui-scale sets a unitless multiplier over rem, which composes with the
reader's own browser font size instead of overriding it. It moves type, spacing,
control heights and icon sizes together, with hard floors so a tap target never
drops below 24px.
Chat messages are typed parts
A message is a list of parts — text, code, reasoning, tool call, file, or a named application component — never a blob of markdown. An assistant turn routinely interleaves prose, a tool call and a confirmation button, and each is a different rendering problem. Applications register their own components by name, so a message can carry working UI:
<Chat
messages={messages}
components={{
approval: (props, ctx) => (
<ApprovalCard {...props} onApprove={ctx.app.approve} />
),
}}
appContext={{ approve }}
/>Props are plain data and components are addressed by name, so a transcript stays serialisable — a component reference in the message would make it impossible to persist or replay.
Setup
Two stylesheets, and you want both. In your application's Tailwind entry:
@import "tailwindcss";
@import "@danxbot/ui/theme.css"; /* @source + the @theme bridge */
@import "@danxbot/ui/styles.css"; /* the token values, base layer and component CSS */That is the whole contract. There is no layer-order statement to remember and no path to configure.
Your Tailwind build compiles this library's utilities, not ours.
theme.css carries @source "./index.js", which points your compiler at the
shipped bundle; the package itself ships zero generated utilities. That is
deliberate and it is the fix for a real defect: a compiled stylesheet emits into
@layer utilities, which is the same layer name your build uses, at equal
specificity, imported last — so the library used to win every tie. Measured in a
consuming app at 1440px, grid grid-cols-1 xl:grid-cols-[1.6fr_1fr] rendered
one column and md:grid-cols-2 xl:grid-cols-3 rendered two. Nothing was
misused; xl: is supposed to beat md: because Tailwind sorts breakpoints,
and that guarantee only holds inside one compiler's output. So there is one
compiler.
theme.css also carries the @theme bridge, extracted from source at build
time, mapping every semantic token onto Tailwind's namespaces — without it your
own bg-accent-subtle compiles to nothing, silently. Order matters:
styles.css comes after, because it supplies the values the bridge points at.
Not using Tailwind 4? Then this package will not style anything: you get the tokens, the base layer and the hand-written component CSS, and no utilities at all. Components render structurally correct and visually unstyled — loudly, on the first screen, rather than as one breakpoint quietly misbehaving. Tailwind 4 in the consuming build is a requirement, not a recommendation.
Fonts are self-hosted woff2 subsets rather than CDN-loaded, because a blocked
CDN renders every design in a system font with no error anywhere. They are
pulled in by styles.css; @danxbot/ui/fonts.css exposes the @font-face
rules on their own if you need them separately.
Preferences are read from <html> attributes so plain CSS and portalled content
both see them:
import { useTheme, useUiScale, useContrast, useMotionPreference } from "@danxbot/ui";ThemeMenu bundles all four into one popover.
Browser support
Modern evergreen browsers — Chrome, Safari, Firefox and modern mobile. That
choice is what makes @property, @starting-style, container queries,
field-sizing and OKLCH available without fallbacks.
Licence
MIT
