One-page LLM doc
Purpose. This single page is the authoritative reference for an LLM that needs to generate code using
chromium-ui-react. It covers installation, conventions, every component, every prop, and a runnable example for each. Hit Copy Markdown (top-right) and paste the result into a chat to prime the model.
1. What this library is
A React component library that reproduces Chromium's native cr_elements design system. Use it to build browser extensions, Google-adjacent web apps, or any surface that should feel like first-party Google UI.
- React 18 / 19 compatible
- Plain CSS — no runtime CSS-in-JS
- Tree-shakeable ESM + CJS bundles,
"sideEffects": ["*.css"] - Automatic dark mode via
prefers-color-scheme(or forced withdata-cr-theme="dark") - Accessible — ARIA, focus rings,
prefers-reduced-motion, Windows High Contrast Mode
2. Install
npm install chromium-ui-react
// Once, at app entry:
import 'chromium-ui-react/styles.css';
Peer deps: react >=18, react-dom >=18.
3. Conventions
- Class prefix. All CSS classes use the
cr-prefix (cr-button,cr-input__field). They come from the bundled stylesheet — you never write them by hand. - Controlled & uncontrolled. Form components (
Input,Checkbox,Toggle,Radio,Select,Tabs) accept eithervalue+onChange(controlled) ordefaultValue+ anyonChange(uncontrolled), following standard React patterns. classNamealways merges. Every component forwardsclassNameand appends it to the internal classes, never replacing them.- Refs. Every interactive component is wrapped in
forwardRef, so you can attach refs for focus management. - Icons. Components accept
ReactNodefor icons — pass any SVG,<img>, or icon-library element (e.g. fromlucide-react). - Theming. Override tokens by redeclaring CSS variables under your own selector — no JS props required.
- No default exports. All components use named exports.
4. Design tokens (most-used)
--cr-fallback-color-primary /* brand blue */
--cr-fallback-color-surface /* default background */
--cr-fallback-color-on-surface /* primary text */
--cr-fallback-color-on-surface-subtle /* secondary text */
--cr-fallback-color-outline /* default borders */
--cr-fallback-color-error /* error red */
--cr-space-1…10 /* 4,8,12,16,20,24,32,40 */
--cr-radius-xs|sm|md|lg|xl|full /* 2,4,8,16,24,100 */
--cr-font-size-xs|sm|md|base|lg|xl|2xl /* 11,12,13,14,16,20,24 */
--cr-elevation-1…5 /* shadows */
--cr-transition-duration /* 80ms, 0ms with reduced-motion */
Full list: Design Tokens.
5. Components
Import everything from the package root:
import {
Button, IconButton,
Checkbox, Radio, RadioGroup, Toggle,
Input, Textarea, SearchInput, Select,
Chip, Badge,
Card, CardHeader, CardBody, CardFooter, CardTitle, CardDescription,
Divider, Toolbar,
Tabs, Tab, TabList, TabPanel, TabsSimple,
Menu, MenuItem, MenuDivider, MenuLabel,
Spinner, Progress,
Toast, Dialog, Tooltip, Link,
List, ListItem, EmptyState,
PanelStack, PanelView, PanelHeader, PanelRow, usePanelStack,
} from 'chromium-ui-react';
Button
Filled, outlined, tonal, destructive, or text button.
Props: variant?: 'outlined' | 'action' | 'tonal' | 'destructive' | 'text' (default outlined), size?: 'sm' | 'md' | 'lg' (default md), fullWidth?: boolean, startIcon?: ReactNode, endIcon?: ReactNode, plus every <button> attribute.
<Button variant="action" onClick={save}>Save</Button>
<Button variant="outlined" disabled>Cancel</Button>
<Button variant="destructive" startIcon={<TrashIcon />}>Delete</Button>
<Button variant="text" size="sm">Learn more</Button>
IconButton
A round, icon-only button. Always requires an aria-label.
Props: variant?: 'standard' | 'filled', size?: 'sm' | 'md' | 'lg', icon: ReactNode, 'aria-label': string (required).
<IconButton aria-label="Close" icon={<CloseIcon />} onClick={onClose} />
<IconButton aria-label="More actions" variant="filled" icon={<MoreIcon />} />
Checkbox
Props: label?: ReactNode, checked?: boolean, defaultChecked?: boolean, indeterminate?: boolean, disabled?: boolean, plus <input type="checkbox"> attributes.
<Checkbox label="Remember me" defaultChecked />
<Checkbox label="Select all" indeterminate />
Radio / RadioGroup
Wrap <Radio>s in a <RadioGroup> to get automatic name binding and controlled value.
RadioGroup props: name?: string, value?: string|number, defaultValue?: string|number, onChange?: (value: string) => void, orientation?: 'vertical' | 'horizontal', disabled?: boolean.
Radio props: label?: ReactNode, value: string | number, disabled?: boolean.
<RadioGroup value={size} onChange={setSize} orientation="horizontal">
<Radio value="sm" label="Small" />
<Radio value="md" label="Medium" />
<Radio value="lg" label="Large" />
</RadioGroup>
Toggle
Material-style switch.
Props: label?: ReactNode, plus <input type="checkbox"> attributes. Renders with role="switch".
<Toggle label="Notifications" checked={on} onChange={(e) => setOn(e.target.checked)} />
Input / Textarea / SearchInput
Single-line text input with optional label, hint, and error.
Input props: label?: ReactNode, hint?: ReactNode, error?: ReactNode, plus <input> attributes.
Textarea props: same, renders <textarea>.
SearchInput props: <input> attributes. Renders a pill-shaped field with a search icon.
<Input label="Email" type="email" placeholder="you@example.com" hint="We'll never share this." />
<Input label="Password" type="password" error="Must be at least 8 characters" />
<Textarea label="Message" rows={4} />
<SearchInput placeholder="Search bookmarks" value={q} onChange={(e) => setQ(e.target.value)} />
Select
Chromium-styled native <select>. Pass options for the 80% case.
Props: label?: ReactNode, options?: { value: string | number; label: string; disabled?: boolean }[], plus <select> attributes. You can also pass <option> children.
<Select
label="Theme"
value={theme}
onChange={(e) => setTheme(e.target.value)}
options={[
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
]}
/>
Chip
Pill button for filters, tags, and input chips.
Props: variant?: 'default' | 'selected' | 'compact', selected?: boolean, startIcon?: ReactNode, onRemove?: (e) => void (shows ✕), plus <button> attributes.
<Chip selected={active} onClick={() => setActive(!active)}>All</Chip>
<Chip variant="compact">v1.2.0</Chip>
<Chip onRemove={() => remove(tag)}>{tag}</Chip>
Badge
Small count indicator.
Props: variant?: 'default' | 'error' | 'success' | 'neutral' | 'warning', plus <span> attributes.
<Badge>12</Badge>
<Badge variant="error">!</Badge>
<Badge variant="success">Online</Badge>
Card + subcomponents
Card props: variant?: 'default' | 'outlined' | 'filled' | 'elevated', interactive?: boolean, plus <div> attributes.
Subcomponents: CardHeader, CardBody, CardFooter, CardTitle, CardDescription — each is a styled <div>/<h3>/<p> you compose freely.
<Card variant="outlined">
<CardHeader>
<CardTitle>Profile</CardTitle>
<CardDescription>Update your personal information.</CardDescription>
</CardHeader>
<CardBody>
<Input label="Name" />
</CardBody>
<CardFooter>
<Button variant="outlined">Cancel</Button>
<Button variant="action">Save</Button>
</CardFooter>
</Card>
Divider
Horizontal/vertical separator.
Props: orientation?: 'horizontal' | 'vertical', subtle?: boolean, inset?: boolean.
<Divider />
<Divider subtle />
<Divider orientation="vertical" />
Toolbar
Side-panel / page header bar.
Props: title?: ReactNode, actions?: ReactNode, tall?: boolean, plus <div> attributes.
<Toolbar
title="Bookmarks"
actions={<>
<IconButton aria-label="Search" icon={<SearchIcon />} />
<IconButton aria-label="More" icon={<MoreIcon />} />
</>}
/>
Tabs
Two APIs: primitive (Tabs + TabList + Tab + TabPanel) for full control, or TabsSimple for the common case.
Tabs props: value?: string, defaultValue?: string, onValueChange?: (value: string) => void.
<Tabs defaultValue="general">
<TabList>
<Tab value="general">General</Tab>
<Tab value="advanced">Advanced</Tab>
</TabList>
<TabPanel value="general">General settings…</TabPanel>
<TabPanel value="advanced">Advanced settings…</TabPanel>
</Tabs>
Or one-liner:
<TabsSimple
defaultValue="a"
tabs={[
{ value: 'a', label: 'First', content: <p>First panel</p> },
{ value: 'b', label: 'Second', content: <p>Second panel</p> },
]}
/>
Menu
Popover list of actions. Position/open logic is up to you — Menu is only the panel.
Subcomponents: Menu, MenuItem, MenuDivider, MenuLabel.
MenuItem props: icon?: ReactNode, end?: ReactNode, selected?: boolean, plus <button> attributes.
<Menu>
<MenuLabel>Account</MenuLabel>
<MenuItem icon={<UserIcon />}>Profile</MenuItem>
<MenuItem icon={<SettingsIcon />} end="⌘,">Settings</MenuItem>
<MenuDivider />
<MenuItem icon={<LogoutIcon />}>Sign out</MenuItem>
</Menu>
Spinner / Progress
Spinner props: size?: 'sm' | 'md' | 'lg', label?: string (defaults to 'Loading').
Progress props: value?: number (0–max), max?: number (default 100), indeterminate?: boolean.
<Spinner />
<Spinner size="lg" label="Fetching comments" />
<Progress value={40} />
<Progress indeterminate />
Toast
A single snackbar/toast cell. Position/stack it yourself (or with a portal).
Props: variant?: 'default' | 'success' | 'error' | 'warning' | 'info', actionLabel?: string, onActionClick?: () => void, onClose?: () => void.
<Toast variant="success" actionLabel="Undo" onActionClick={undo}>
Deleted 1 item.
</Toast>
<Toast variant="error" onClose={dismiss}>Could not save.</Toast>
Dialog
Modal dialog rendered into a portal on document.body.
Props: open: boolean, onClose?: () => void, title?: ReactNode, actions?: ReactNode, closeOnBackdrop?: boolean (default true), closeOnEscape?: boolean (default true).
<Dialog
open={open}
onClose={() => setOpen(false)}
title="Delete bookmark?"
actions={<>
<Button variant="outlined" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="destructive" onClick={confirmDelete}>Delete</Button>
</>}
>
This action can't be undone.
</Dialog>
Tooltip
CSS-only tooltip that appears on hover or focus.
Props: content: ReactNode, placement?: 'top' | 'bottom' | 'left' | 'right'.
<Tooltip content="Save (⌘S)">
<IconButton aria-label="Save" icon={<SaveIcon />} />
</Tooltip>
Link
Props: subtle?: boolean, plus <a> attributes.
<Link href="/docs">Read the docs</Link>
<Link href="/changelog" subtle>View changelog</Link>
List / ListItem
Structured row for list UIs (bookmarks, settings rows, command palette).
ListItem props: icon?, avatar?, primary?, secondary?, end?, interactive?, selected?, dense?.
<List>
<ListItem
interactive
icon={<FolderIcon />}
primary="Reading list"
secondary="12 items"
end={<Badge>12</Badge>}
/>
<ListItem
interactive
dense
primary="Docs"
secondary="chromium-ui-react.dev"
/>
</List>
EmptyState
Centered placeholder for empty lists / zero results.
Props: icon?, title?, description?, action?.
<EmptyState
icon={<InboxIcon />}
title="Nothing here yet"
description="Bookmarks you save will appear here."
action={<Button variant="action">Add bookmark</Button>}
/>
PanelStack / PanelView / PanelHeader / PanelRow
Drill-in navigation inside a side panel. Native Chromium pattern — click a row, a sub-page slides in from the right; a back-arrow slides it back. The only composite group in the library; everything else is a primitive.
PanelStack props: defaultView?, value? (controlled), onChange?(view), transitionDuration?: number (ms, default 240).
PanelView props: id: string (required).
PanelHeader props: title?, back?: boolean, onBack?, leading?, actions?.
PanelRow props: primary?, secondary?, icon?, end?, navigateTo?: string, chevron?, interactive?, disabled?.
Hook: usePanelStack() → { current, stack, push(id), pop(), reset(id) }.
<PanelStack defaultView="main">
<PanelView id="main">
<PanelHeader title="Extension panel" />
<PanelRow primary="Source" secondary="Current tab" end={<Badge variant="success">ready</Badge>} />
<PanelRow primary="Include nested items" end={<Toggle defaultChecked />} />
<PanelRow primary="Advanced options" secondary="Output, filters, columns" navigateTo="advanced" />
</PanelView>
<PanelView id="advanced">
<PanelHeader title="Advanced options" back />
{/* form, radios, checkboxes... */}
</PanelView>
</PanelStack>
Give it an explicit height (or put it inside a min-height: 0 flex parent) — otherwise views collapse to zero.
6. Composition cheat-sheet for browser extensions
Side-panel layout
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<Toolbar title="My extension" actions={<IconButton aria-label="Settings" icon={<SettingsIcon />} />} />
<TabsSimple
defaultValue="items"
tabs={[
{ value: 'items', label: 'Items', content: <ItemsList /> },
{ value: 'history', label: 'History', content: <History /> },
]}
/>
</div>
Settings row
<Card variant="outlined">
<CardBody>
<List>
<ListItem primary="Show notifications" end={<Toggle checked={on} onChange={...} />} />
<Divider />
<ListItem primary="Sync across devices" end={<Toggle />} />
</List>
</CardBody>
</Card>
Confirmation dialog
<Dialog
open={open}
onClose={close}
title="Clear all bookmarks?"
actions={<>
<Button variant="outlined" onClick={close}>Cancel</Button>
<Button variant="destructive" onClick={clearAll}>Clear</Button>
</>}
>
This will permanently remove 247 bookmarks from this device.
</Dialog>
7. Common pitfalls
- Forgetting the stylesheet. If components render unstyled, you forgot
import 'chromium-ui-react/styles.css'. - Using inline color hex codes. Prefer semantic tokens (
var(--cr-fallback-color-primary)) so dark mode stays consistent. - Building custom icon buttons with
<Button>. Use<IconButton>— it renders a proper circular ripple container. - Writing dark-mode media queries. Don't — the library does this automatically on its tokens. Use the tokens.
- Rendering
TaboutsideTabs. It needs the context provider. Same forRadioinsideRadioGroup(optional but recommended).
8. Accessibility checklist
- Every
IconButtonmust havearia-label. Dialoglocks focus and trapsEscape; do not disable these defaults unless you have a specific reason.Togglerenders withrole="switch".Checkboxwithindeterminateprop drives nativeindeterminatestate, not a third value.- All focusable elements render a visible focus ring — don't override
:focus-visiblewithout replacing it.
9. Versioning
Minor versions may add new components. Breaking changes bump the major. Pin if you care.