@uix-design/react
v0.13.0
Published
React components, theme provider, forms, navigation, overlays, and data display primitives for UIX Design.
Downloads
1,217
Maintainers
Readme
@uix-design/react
UIX Design is a React component system for operational products: forms, data-heavy workspaces, navigation, overlays, feedback, and runtime theming.
File upload
FileUpload provides a file picker, drop zone and bounded upload queue with the
same heading, footer, accent, density and automatic nested corners as UIX panels.
It does not choose a storage service or read file contents for previews.
Use appearance="plain" inside an existing Dialog, Card or form. It removes the outer
frame, shadow, heading and body padding. Empty, it shows only the drop zone; selected
files retain their queue, errors, progress, summary and manual upload action. title
remains the accessible name and description remains accessible helper text. The host
owns the surrounding spacing. The default appearance="panel" keeps the standalone panel.
import { FileUpload, type FileUploadHandler } from "@uix-design/react";
const upload: FileUploadHandler = async (file, { signal }) => {
const body = new FormData();
body.append("file", file);
const response = await fetch("/api/files", { method: "POST", body, signal });
if (!response.ok) throw new Error("Upload failed");
};
<FileUpload
title="Project attachments"
accept=".pdf,image/*"
maxFiles={10}
maxFileSize={50 * 1024 * 1024}
concurrency={2}
onUpload={upload}
onUploadComplete={(file) => console.log(file.name)}
/>;onUpload(file, { signal, onProgress })must resolve only when the server has accepted the file, and reject for HTTP, network or timeout failures. Respectsignalto abort the request. Report actual file bytes throughonProgressif your XHR/SDK transport supports it; fetch uploads remain indeterminate. Even at 100% bytes sent, the UI waits for confirmation before displaying completed.- Automatic upload is the default.
autoUpload={false}exposes an explicit start action. WithoutonUpload, files stay ready for a form; read theirFilereferences fromonFilesChange(items). Queue ownership stays in the component. Remount with a different Reactkeywhen starting a different form/session. accept,maxFileSize(50 MiB default),maxFiles(10 retained files default),multipleandvalidateFileapply equally to selection and drop. Duplicates are compared by name, size, MIME type and last-modified metadata without hashing file contents.onRejectreceives rejected files and structured causes while valid files still enter the queue. Server-side validation remains required.- Cancel aborts that request; retry creates a fresh request. Late progress or
responses from canceled attempts are ignored. Removing a completed item only
changes the local list; it does not delete the server file.
disabledblocks new actions while active requests can still finish. - Concurrency defaults to 2 and is clamped to 1–6. Progress is coalesced every
100 ms; unrelated rows are memoized. Unmount aborts active requests and cleans
timers. No file-to-base64 conversion, object URLs, polling, hashing or new
dependencies are introduced. Set a sensible
maxFilesfor the host device; the list scrolls but does not virtualize arbitrarily large queues. Multipart upload, persistence and resumable transfers belong to the transport. - All copy is replaceable through
labels;localeformats byte counts. Native buttons support keyboard operation, status updates use a polite live region, and percentages do not continually interrupt screen readers.radiusandnestedRadiusshare the existing Card surface contract.
The File Upload docs page includes an example inside Card, manual confirmation, disabled controls, a recoverable failure and selection-only mode. Its transport is explicitly simulated and never sends selected files to a server.
Button interaction colors
Neutral circular actions use the runtime accent for their icon on hover, keyboard
focus and press, with a restrained border mixing 30% accent into the base outline. Outline buttons with tone="primary", tone="accent"
or tone="secondary" use the same interaction color for text, icons and border.
The resting state remains subdued. Solid and semantic tones retain their contrast,
appearance="borderless" stays borderless, and disabled or busy actions do not
adopt the interactive accent. Calendar navigation shares IconButton behavior.
Use IconButton appearance="glass" for frosted controls over photos or rich media.
It uses the provider's glassOpacity, retains the subtle interaction border, and
keeps blur while hovering or pressing. Unsupported backdrop filters and reduced
transparency preferences receive an opaque surface.
<IconButton appearance="glass" label="Next image" onClick={next}>
<ChevronRight />
</IconButton>Advanced search
SearchPalette opens at the top of the viewport with safe-area spacing on desktop
and mobile. Filtering and keyboard navigation keep that position stable: only the
results scroll, while the search field, categories and footer remain visible.
It shares the workspace surface, 24px radius and 12px chrome insets. The React docs
include a dedicated Advanced Search page with categories, empty results and
selection feedback. The default shortcut is Cmd/Ctrl + K; use shortcut={false}
when another palette already owns it.
Page headings
Use PageHeader with tone="accent" for a prominent, responsive page title in
the active runtime accent, with a neutral eyebrow and secondary description.
The default tone="neutral" preserves the existing heading appearance.
<PageHeader
eyebrow="UI library"
title="Calendar"
tone="accent"
meta={<p>Meetings, reservations and team schedules.</p>}
/>Advanced components
AICopilot, FileManager, ImageGallery, Spreadsheet, Calendar, Gantt, RichTextEditor and FormSequenceBoard now belong to @uix-design/advanced. See the migration guide. Advanced is an optional public package.
Automatic styles
Component imports include their compiled CSS and shared foundation automatically. The optional @uix-design/react/styles export loads all core styles for legacy integrations; avoid combining it with automatic styles. Component subpaths such as @uix-design/react/actions are also available.
Button and IconButton load separate styles, and internal loading states retain only the circular indicator. Full Spinner variants remain available when importing Spinner explicitly. See performance budgets for measured consumer sizes and package validation.
Install
pnpm add @uix-design/react @uix-design/tokensStyles load automatically with the components; no additional CSS import is needed.
Match mobile browser surfaces
Enable edge-to-edge layout in the host document and provide an initial solid color so Safari and Chrome do not flash an unrelated surface before React starts:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#ffffff" />Then opt into runtime synchronization. The provider follows manual light/dark changes, keeps the document root fallback in sync, and restores any pre-existing browser surface configuration when it unmounts.
<DesignSystemProvider browserThemeColor theme="light">
<App />
</DesignSystemProvider>Pass { light: "...", dark: "..." } to browserThemeColor when the product header uses custom
solid fallback colors. AppShell keeps controls inside device safe areas when
viewport-fit=cover is present. Set edgeToEdge only on the viewport-level shell so Safari can
compose its top edge from the attached header while embedded shell previews remain contained.
Browser chrome transparency remains platform-controlled; use a solid fallback color that visually
matches the composed header surface.
On small-screen Android devices, Chrome 135 and later use viewport-fit=cover as the native
edge-to-edge opt-in: the page can reach the gesture-navigation area from first paint without
Chrome's temporary bottom chin. UIX keeps fixed actions and feedback, plus drawer footers, above
the dynamic safe area while allowing the page surface to remain visible underneath system chrome.
In a normal browser tab, Chrome still owns the address bar's shape, placement, material, and
opacity; a website can coordinate its color and surrounding surface, but cannot force Safari's
floating-toolbar appearance.
Each shell can also choose independent in-page safe-area surfaces without adding styles to the host documentation. The top keeps the header surface by default; a transparent bottom lets the native Safari toolbar or Chrome gesture-navigation area compose over the page:
<AppShell edgeToEdge safeAreaSurfaces={{ bottom: "transparent" }}>
<Workspace />
</AppShell>Start with a themed operational form
DesignSystemProvider sets the theme, density, runtime accent, and sidebar surface for its
children. Field wires labels, hints, required state, and validation feedback to compatible
controls.
WorkspaceSidebar uses indentation and connectors to express option hierarchy. Branches and
pages share the same base typography; an active ancestor uses accent text without a selected
background. Only the current page receives the soft background and contrasting neutral marker,
including on accent sidebar surfaces. Mark the destination option active; its ancestors are
derived automatically.
import {
Button,
Card,
DesignSystemProvider,
Field,
Form,
Input,
Textarea
} from "@uix-design/react";
export function CreateProjectForm() {
return (
<DesignSystemProvider accentColor="#2563eb" density="compact" theme="light">
<Card>
<Form>
<Field hint="Shown to teammates in the workspace." label="Project name" required>
<Input name="projectName" placeholder="Q3 collections" required />
</Field>
<Field label="Context">
<Textarea name="context" placeholder="What should this project accomplish?" />
</Field>
<Form.Footer>
<Button type="submit">Create project</Button>
</Form.Footer>
</Form>
</Card>
</DesignSystemProvider>
);
}Handle focused operational decisions
Compose Dialog with its semantic parts for short, blocking workflows. The component manages
keyboard dismissal, focus handling, and the dialog relationship for you.
import { useState } from "react";
import { Button, ButtonCluster, Dialog } from "@uix-design/react";
export function PublishReportAction() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Publish report</Button>
<Dialog open={open} onOpenChange={setOpen}>
<Dialog.Content size="sm">
<Dialog.Header>
<Dialog.Title>Publish the collections report?</Dialog.Title>
<Dialog.Description>
The report will become visible to Finance and Operations.
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<ButtonCluster align="end">
<Dialog.Close asChild>
<Button variant="ghost">Cancel</Button>
</Dialog.Close>
<Button onClick={() => setOpen(false)}>Publish</Button>
</ButtonCluster>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
</>
);
}Build feedback and local navigation
Use Alert for persistent context, Toast for transient feedback, and Tabs for accessible
local navigation. All three support theme, density, and runtime accent settings from the provider.
import { Alert, Button, Tabs, Toast } from "@uix-design/react";
const reviewTabs = [
{
content: <p>128 invoices are waiting for review.</p>,
label: "Queue",
value: "queue"
},
{
content: <p>12 invoices were escalated today.</p>,
label: "Escalations",
value: "escalations"
}
];
export function CollectionsWorkspace() {
return (
<>
<Alert title="Sync delay" tone="warning">
The warehouse feed is 12 minutes behind. Review the latest successful import before
approving invoices.
</Alert>
<Tabs ariaLabel="Collections workspace" items={reviewTabs} responsive="menu" />
<Toast
action={<Button size="sm">Review</Button>}
description="7 invoices require approval before 16:00."
title="Collections digest"
tone="success"
/>
</>
);
}Included building blocks
- Actions: buttons, icon buttons, button groups, and menus.
- Forms: fields, inputs, selects, choices, sliders, switches, and validation helpers.
- Data display: tables, data-table shells, trees, timelines, collections, and spreadsheet tools.
- Navigation: tabs, pagination, navbar, scrollspy, tooltips, and workspace sidebar.
- Overlays and feedback: dialogs, drawers, popovers, dropdowns, alerts, and toasts.
- Theming: light/dark mode, compact density, runtime accent colors, and sidebar surfaces.
Pagination
Pagination is shared by DataTableShell and standalone navigation. It uses 12px
control corners, transparent inactive pages and a restrained runtime-accent current
page. Previous/next controls retain visible borders and keyboard focus. Numeric
navigation switches to a current/total summary on small screens or in table footers
narrower than 36rem; compact/select variants remain available explicitly.
Table footers retain the 24px surface contour, symmetric 12px insets, and an outlined rows-per-page Select. Control sizing follows density, with 44px minimum targets on small screens and coarse pointers. Numeric mode avoids allocating options for every page; the select variant creates the requested page options.
List groups
ListGroup shares the 24px workspace surface radius and neutral panel/border tokens.
Joined rows subtract the frame border from their outer corners; separator="space"
gives each row its own matching surface. Minimal lists use a 12px outer contour. Joined rows round only the first
row’s top corners and the last row’s bottom corners; spaced rows round all four. Insets are symmetric, selection uses a restrained accent fill and current
label, and auxiliary Buttons, IconButtons and form controls retain their own sizing.
Keyboard focus and action menus remain visible without clipping the list container.
Use integration="standalone" (default) for an independent surface. Integration is
explicit: placing a ListGroup inside a Card does not change it automatically.
appearance applies to standalone and inset lists. Inset lists default to minimal;
use appearance="bordered" for an inner frame. Edge lists always resolve to minimal,
retaining selection, focus and row separators. Legacy flush applies to standalone lists.
integration="inset": optional frame throughappearance. A stable 12px outer contour matches Buttons and collapsed CodeBlocks in every density. Joined rows have square corners along their dividers; only exposed outer corners are rounded.integration="edge": no extra frame or shadow. Only corners touching the parent frame are rounded. UseedgeCorners="all"(default),"top","bottom"or"none". With a header use"bottom"; with a footer use"top"; with both use"none".
<Card>
<ListGroup integration="inset" appearance="bordered" items={items} />
</Card>
<Card title="Sections" bodyPadding="none">
<ListGroup integration="edge" edgeCorners="bottom" items={items} />
</Card>
<Card title="Sections" bodyPadding="none" footer={<Button>Save</Button>}>
<ListGroup integration="edge" edgeCorners="none" items={items} />
</Card>Card bodyPadding="none" removes only body padding, preserving header/footer
spacing and control sizing. Edge mode requires a parent with no content inset;
it does not expand through padding or clip descendants. Geometry defaults to Card
tokens. For other edge containers, set --uix-list-group-parent-radius
and --uix-list-group-parent-border to their actual
geometry. Edge mode subtracts the parent border from its radius; inset mode
retains the control radius independently of density.
Automatic nested surface radii
Card provides its remaining corner space to compatible framed children by default: Card, CodeBlock, Accordion, panel Tree, FileUpload, media surfaces and participating Advanced workspaces. No geometry props are required:
<Card title="Review">
<Accordion items={sections} />
<CodeBlock title="Source" code={source} />
</Card>The inner radius is bounded by max(0px, outer radius - padding - border).
Inside Card, Grid mode="flow" rows use CSS grid gaps between columns instead
of exterior half-gutters. Content aligns with the Card header and footer, even
across nested flow grids or multiple full-width columns in a single row. The Card
is the only owner of the outer inset; bodyPadding="none" stays flush without
negative margins or horizontal overflow. Responsive flow columns and ordinary
non-scrollable Forms transmit the radius bound without accumulating extra insets.
Custom/padded layout boundaries retain their actual inset. At a shared Card edge, compatible
surfaces use the concentric radius; remaining corners cannot exceed the same
available space, including between text or with a Card header and footer.
There is no special minimum curve for folded code headers. Separated Accordion
panels each adapt all four corners, including middle items, in open and closed
states; grouped items retain their shared frame and straight internal separators.
The calculation uses CSS variables, responds to density and explicit Card radii,
and adds no observers, DOM measurements or ancestor traversal. Arbitrary wrappers,
offset columns and custom-styled layouts are geometry boundaries because
their inset is unknown. Buttons, fields, pills, avatars, icons and floating panels
keep their independent shape. Use nestedRadius={false} or an explicit radius
only when a framed child needs independent geometry. Custom framed components can
participate with data-uix-surface="auto" and --uix-surface-radius.
Preview-first code examples
CodeBlock uses the shared workspace surface and 24px radius. Its persistent
header contains a code icon, title, optional description, actions and an
accessible expand/collapse button. The secondary toolbar shows the language
before the bounded file selector. Controls use shared IconButton styles and
tooltips; copy success is announced through a live region.
The entire header is the expand/collapse hit area, with a pointer cursor and a
visible keyboard focus ring. Enter and Space operate the same native button.
Copy, download, custom actions and interactive title/description content remain
independent sibling targets and never toggle the block.
When files is nonempty, copy lives beside the file selector and is labelled
"Copy file" by default; it copies only the active file. Single snippets keep copy
in the header. File actions collapse with the selector and code.
Blocks are expanded and collapsible by default. Existing examples that explicitly
set collapsible still start folded, unless defaultExpanded is provided.
Set collapsible={false} to keep a block permanently expanded. expandLabel
and collapseLabel localize the toggle. Collapsing preserves the active file and
keeps the header actions available; hidden file controls leave the tab order.
The expanded block includes a footer with logical line and Unicode character
counts for the displayed code (after trimming trailing whitespace), plus the
active file position when files is used. It updates with the selected file.
footer accepts custom React content or a renderer receiving CodeBlockMetadata:
lineCount, characterCount, fileIndex (one-based, zero for snippets) and
fileCount. Use the renderer to translate labels or add application-specific
context. Pass footer={null} or footer={false} to hide it. The footer collapses
with the code; metadata is informational and does not announce every file change
as a live region.
<CodeBlock
title="Component example"
description="Copy the source or collapse the preview."
code={"<Button>Save changes</Button>"}
language="tsx"
collapsible
expandLabel="View code"
collapseLabel="Hide code"
/>Pass files: readonly CodeBlockFile[] to place a file selector in the secondary toolbar. Each
file has a unique path, complete code and optional language (inferred from the
extension otherwise). Nonempty files take precedence over code. Selection is
uncontrolled with defaultActiveFile, or controlled with activeFile and
onActiveFileChange. Missing selections fall back to the first file; an empty
array retains the single-code API. Copy preserves the selected file's exact text,
including trailing newlines, and changing files keeps focus in the selector.
actions accepts block-level React controls for sharing, opening an editor or other
app-owned actions in the header. downloadHref adds a native download link styled
with the shared UIX IconButton styles. downloadScope="block" (default) keeps project
downloads in the persistent header; downloadScope="file" places a file download
beside the selector and copy action. The application must update the URL as the
active file changes; the component never converts a project archive into a single
file download. File downloads default their filename to the active path's basename.
Custom actions can compose Tooltip and IconButton to match. The file selector is capped at --uix-code-block-file-width (24rem by default) and shrinks on narrow screens. Set downloadFileName, downloadLabel and
fileSelectLabel to customize the filename and accessible labels. The URL can
point to the current file or a complete project archive; browsers honor download
for same-origin, blob and permitted data URLs. Data downloads are restricted to
plain text, CSV, JSON, PDF, ZIP, octet-stream, PNG, JPEG, GIF, WebP and AVIF; HTML
and SVG data URLs are rejected. Header actions remain visible when the content is collapsed. Use --uix-code-block-max-height to adjust the multi-file code area's
32rem maximum height without losing access to any code.
Data-driven links in ListGroup, Navbar, Breadcrumbs, SidebarNav and notification attachments accept HTTP(S), mailto, tel and relative destinations. Executable or unsupported schemes and control characters are rejected before rendering a destination. Blocked destinations retain their labels without enabling navigation. Applications remain responsible for restricting destination hosts when required.
<CodeBlock
files={[
{ path: "src/App.tsx", code: appSource, language: "tsx" },
{ path: "src/app.scss", code: stylesSource, language: "scss" }
]}
defaultActiveFile="src/App.tsx"
fileSelectLabel="Archivo del proyecto"
downloadHref="/downloads/project.tgz"
downloadFileName="project.tgz"
downloadLabel="Descargar proyecto"
actions={<Button onClick={shareProject}>Compartir</Button>}
showLineNumbers
/>Runtime accents preserve the exact chosen accentColor on primary filled actions and
accent sidebar surfaces, in both light and dark themes. The shared foreground is white
on the Orange, Red, Rose, Pink, Violet, Purple, Fuchsia, Indigo, Slate and Graphite
presets as an explicit brand preference. These exact-color/white-text combinations
are not all WCAG AA compliant for small text. Other accents use white when its
contrast reaches 4.5:1; otherwise they use dark navy (#14283f), deepening only
the text if a mid-tone accent needs more contrast. Bright colors such as sky blue and
yellow retain their original background.
--uix-accent-solid and --uix-accent-sidebar match --uix-accent;
--uix-accent-on-solid and --uix-accent-contrast provide the paired foreground.
--uix-accent-solid-hover, --uix-accent-solid-active, and
--uix-accent-state-color move state backgrounds away from the foreground to preserve
contrast. Sidebar labels use opaque ink, including secondary labels; hierarchy comes
from typography. Runtime setAccentColor updates the complete pair without consumer
changes. Busy buttons
retain their label, icon slots and dimensions. A visible Field error infers an invalid
control state unless an explicit validation state takes precedence.
Consistent controls and floating surfaces
DropdownMenu shares the input shell, density and appearance tokens. Use size="sm" | "md" | "lg"
and appearance="default" | "underlined" | "outlined" | "inset" to match adjacent inputs.
The trigger remains an accessible menu button; use Select for a form value. disabled
disables the trigger.
<DropdownMenu
appearance="outlined"
size="md"
trigger="Project actions"
triggerAriaLabel="Project actions"
items={[{ label: "Edit", onSelect: handleEdit }]}
/>Chip suggestions render in a themed portal, follow the field when scrolling or resizing,
and open above when space below is limited. CloseButton uses a discreet circular target with a transparent neutral surface,
including the date picker close control; its radius can be
customized with --uix-close-button-radius.
Fixed ToastStack notifications have a minimum height of 5.5rem and a light/dark-aware
shadow (--uix-toast-stack-shadow). Inline previews keep their own sizing and elevation;
compact icon-only notifications keep their compact size. Spreadsheet tools share the
system surfaces and accent, and display disabled editing actions on read-only sheets.
Button appearance="link" uses a short underline that extends on hover or keyboard focus,
with no filled surface or elevation. Solid button elevation is scoped to solid appearance,
including hover, so overriding a variant with outline, soft, ghost or link does not inherit
its solid highlight. Reduced-motion preferences disable the underline animation.
Spreadsheet uses the same outer border and radius tokens as DataTableShell. Its grid
preserves the declared or resized column widths, including the fixed row-number
gutter. Sheets with few columns leave unused viewport space instead of stretching
the index; overflow remains horizontally scrollable. There is no inset grid frame.
Headings, formula, toolbar and status bars use the shared 12px surface inset on
desktop and mobile. --uix-spreadsheet-padding and --uix-spreadsheet-toolbar-padding
allow explicit overrides; --uix-spreadsheet-chrome-gap controls tool-group spacing.
Native Alert dismissal places the
close control alongside its content, with matching top and end insets.
Form filters and adaptive action bars
FilterMenu uses the same appearance and size as Input. Set fullWidth to fill a
form column and connect a visible Field label using htmlFor / triggerId.
disabled disables the popover trigger. These trigger options apply to variant="popover";
the inline variant has a non-interactive heading. Selected options use native checkbox/radio
indicators. Multiple selections keep neutral rows, with accent reserved for the
checkbox and count; single selection retains a subtle selected surface.
Filter panels share the 24px workspace radius, neutral surface tokens and symmetric 12px insets. Solid panels use muted search/footer surfaces; glass panels share one frosted background. Option corners derive from the panel radius and inset. Inline panels stay flat, while popover panels retain overlay elevation. The clear action uses the standard Button, including density, keyboard focus and touch sizing. Long option labels wrap within the panel.
<Field htmlFor="status-filter" label="Status">
<FilterMenu
triggerId="status-filter"
label="Select statuses"
appearance="outlined"
size="md"
fullWidth
showTriggerCount
options={[
{ label: "Active", value: "active" },
{ label: "Pending", value: "pending" }
]}
/>
</Field>The floating filter panel keeps its intrinsic width, even beside a wide form trigger.
DropdownMenu also exposes triggerId and matchTriggerWidth (default true); custom
panels can set matchTriggerWidth={false}.
ActionBarTitle supplies a truncating contextual title. ActionBar mobileLayout="actions"
prioritizes the actions below 40rem and visually hides the non-interactive start/content
slots while preserving live-region announcements. Do not put interactive content in those
slots with this mode. The default mobileLayout="preserve" retains existing behavior. AppShell measures its
header, including safe-area padding, so density changes and taller header content
keep the top ActionBar and scrolling content below the visible header.
<ActionBar
mobileLayout="actions"
start={<ActionBarTitle>Project settings</ActionBarTitle>}
actions={
<ButtonCluster>
<Button variant="primary">Save</Button>
</ButtonCluster>
}
/>Accordion uses a rotating chevron and a content panel separated from its heading by a divider. The heading always stays above the content at every width. Content surfaces use equal spacing on all sides. Custom expand/collapse icons remain supported. Breadcrumbs distinguish the current destination from ancestor links. Navbar uses rounded navigation items with a short active indicator, switching to a leading rail in its collapsed mobile layout. All of these styles ship with the package; consumers need no component-specific CSS or inline styles.
Alert's default soft appearance uses a flat semantic tint, a separate icon tile, and stronger title hierarchy. Its insets remain equal on all sides. Outline, minimal and solid appearances remain distinct. Alert actions wrap below the message in narrow inline containers. Toast concentrates its semantic tone in the status icon and progress. Floating Toast stacks retain the same content-driven spacing as inline Toasts, with elevation.
DateTimePicker timezone, month and year options share selection geometry, spacing and focus states, including the slim accent marker. Their list surfaces have a more visible neutral background and equal insets that remain visible while scrolling. Native scrollbar gutters are measured inside the component so they do not create a wider margin on one side.
License
MIT. Copyright (c) 2026 José L. Alvarado Santamaría.
Feedback surfaces and corner geometry
Alert and Toast share calm tinted surfaces, contained semantic icons and clear
text hierarchy. soft uses a flat tint, elevated uses a neutral surface with an
inset semantic rail and circular icon, and Alert minimal provides compact
contextual feedback with a rail. Minimal alerts keep an 8px outer inset in every
density and a 44px dismiss target. Toasts also keep an 8px outer inset and 44px
dismiss target; their height follows the message and actions, including in fixed
stacks, without an extra minimum height. Dismissal, actions, progress and live-region
behavior retain their existing APIs. Colors continue to follow theme, semantic
state and accentColor. Icon-only Toasts use one status surface; demos include
visible text when a symbol alone would leave the outcome unclear.
Card, Table, Spreadsheet, Calendar, RichTextEditor and ImageGallery share a 24px
outer radius through --uix-card-radius and --uix-radius-surface. Card header,
body and footer use symmetric density-aware padding (12px in compact mode).
Controls keep their own sizes and radii. Small panels retain their 8px radius;
Alert/Toast retain 12px, and dialogs and floating ActionBars retain 16px.
Text buttons default to 12px corners. Compact CloseButton sizes (xs and sm)
retain their visual circle on narrow or touch screens, reserving at least 44px
for interaction toward the inline start and block end. This reserved space stays
in layout so adjacent controls do not overlap the expanded target. Overlay-specific
close sizes continue to take precedence.
Density changes spacing without enlarging these outer radii. Explicit component
radius overrides remain available. Alert/Toast icon tiles keep their independent
--uix-alert-inner-radius / --uix-toast-inner-radius values.
Concentric radius helpers apply to adjacent nested contours, such as dropdown
items and inset button groups. Choose the exterior first and derive the interior
with radius-inner(outer, inset, border), clamped at zero. Content padding alone
is not a reason to enlarge a surface; independent icons, pills and controls retain
their own shapes.
Workspace headings in Spreadsheet, Calendar, Table, RichTextEditor, ImageGallery and AICopilot use symmetric 12px insets. Their icon tiles and header actions use 32px on desktop and 44px on touch screens, independent of content density, instead of imposing a fixed-height header. Spreadsheet adds rows and columns through one contextual insertion indicator with a 20px visible circle and 32px desktop / 44px touch targets. Descriptions and wrapped titles can naturally increase heading height; custom action slots continue to respect the sizes chosen by the consumer.
In Spreadsheet, Calendar, Table, RichTextEditor and ImageGallery, the icon, title and actions align to the first heading row. Descriptions and metadata grow below it without vertically recentering the buttons, preserving the 12px top inset even in multi-line headings.
DataTableShell and RichTextEditor accept headerActions for buttons beside
the title. In containers narrower than 36rem, those actions and ImageGallery's
renderActions move below the heading. Table's existing actions and toolbar
remain in the separate toolbar, which also keeps 12px insets. Use the default
button size to match the heading icon; the container does not override button
sizes, disabled state, or loading state.
<DataTableShell title="Requests" rows={rows} def={def}
headerActions={<Button onClick={addRequest}>New request</Button>}
actions={<Button variant="secondary" onClick={exportRows}>Export</Button>} />
<RichTextEditor label="Notes" value={html} onChange={setHtml}
headerActions={<Button onClick={saveNotes} busy={saving}>Save</Button>} />Distinct density modes
DesignSystemProvider density="compact" | "comfortable" | "spacious" adjusts
controls, navigation rows, panel insets and layout gaps together. Standard desktop
controls measure 32 / 40 / 48px, card padding 12 / 20 / 28px and layout gaps
8 / 12 / 16px. Font sizes and restrained surface radii remain stable. Primary
controls keep a 44px minimum on small screens and coarse pointers. Tables inherit
the provider's density; an explicit density or legacy dense prop takes priority.
Breadcrumbs inside an application shell
Use ActionBar wrap mobileLayout="preserve" start={<Breadcrumbs ... />} for
navigation that must stay available on small screens. The bar stacks its slots
below 900px. overflow="truncate" keeps a path on one line while preserving each
full accessible label. maxItems moves intermediate ancestors into a keyboard
accessible popover; the current page remains visible and uses aria-current.
Set mobileMaxItems={2} to move all ancestors into that popover below 48rem,
leaving the available width to the current destination.
Each ancestor accepts a real href and an optional onClick router handler. Only
intercept unmodified primary clicks, preserving open-in-new-tab and copy-link
behavior. Current and disabled items never invoke the link handler. The documentation
bar demonstrates real platform routes; the Breadcrumb page also includes a projects
demo with URL-backed navigation and browser back/forward support.
For a single mobile navigation row, pair ActionBar mobileLayout="navigation"
with Button iconOnlyOnMobile leadingIcon={...}. Below 48rem the button becomes a
square icon target of at least 44px; its text remains in the accessibility tree.
Without a leading icon the label stays visible. Desktop keeps the icon and label.
Use meaningful labels and a title for pointer users. The docs reading bar uses
this composition for code and copy-link actions.
Indicator cards
KpiStrip has been removed. Compose indicators with Card inside Grid.Col
instead: map the existing items to responsive columns, use Card.Meta for labels
and supporting text, and render the value as normal Card content. This composition
shares Card's shape, padding and theme rather than maintaining a second surface.
Tables with typed column filters
DataTableShell loading displays a centered status with loadingMessage during
the initial request. Keep the last rows in rows when refreshing: the table,
totals and pagination stay in place beneath a translucent loading layer, with
interaction blocked until the request completes. EmptyState is reserved for a
successful response with no records or no matching results.
DataTableShell keeps column filters immediately below the headers. Opt into typed
filters with columns[].filter; existing string-based filterValues and columns
without a filter definition retain their previous text-search behavior. Column
type: "number" controls alignment; it does not silently change legacy filtering.
import {
DataTableShell,
DesignSystemProvider,
type DataTableDef,
type TableFilterValues
} from "@uix-design/react";
import { useState } from "react";
type Request = {
id: string;
name: string;
team: string;
amount: number | null;
date: string;
approved: boolean;
};
const rows: Request[] = [
{
id: "OP-001",
name: "Licenses",
team: "platform",
amount: 100,
date: "2026-09-12",
approved: false
},
{
id: "OP-002",
name: "Capacity",
team: "support",
amount: 1100,
date: "2026-09-13",
approved: true
}
];
const def: DataTableDef<Request> = {
rowKey: "id",
columns: [
{
id: "name",
header: "Request",
accessorKey: "name",
size: 260,
filter: { type: "text", match: "contains", placeholder: "Name…" }
},
{
id: "team",
header: "Team",
accessorKey: "team",
size: 190,
filter: {
type: "select",
searchable: true,
options: [
{ label: "Platform", value: "platform" },
{ label: "Support", value: "support" }
]
}
},
{
id: "amount",
header: "Amount",
accessorKey: "amount",
type: "number",
size: 180,
filter: { type: "number", step: 0.01 }
},
{ id: "date", header: "Date", accessorKey: "date", size: 220, filter: { type: "date" } },
{
id: "approved",
header: "Approved",
accessorKey: "approved",
size: 180,
filter: { type: "boolean", trueLabel: "Approved", falseLabel: "Pending" }
}
],
features: {
search: {
mode: "row",
defaultColumnFilterValues: { amount: { operator: "between", value: 0, max: 500 } },
defaultFiltersVisible: true,
defaultStickyFilterRow: true
},
pagination: { pageSize: 10, pageSizeOptions: [10, 20, 50] },
columnVisibility: true,
sorting: true
},
layout: { stickyHeader: true, maxHeight: "30rem" },
display: { density: "comfortable", allowDensityChange: true }
};
function Requests() {
return (
<DesignSystemProvider theme="dark" applyToDocument={false}>
<DataTableShell
title="Requests"
description="Filter each column to refine the results."
def={def}
rows={rows}
/>
</DesignSystemProvider>
);
}Filter definitions and values
| columns[].filter | columnFilterValues[columnId] example | Behavior |
| --------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------ |
| { type: "text" } | "lic" | Case-insensitive contains; whitespace around the query is ignored. |
| { type: "text", match: "startsWith" } | "OP-" | Prefix matching. |
| { type: "text", match: "equals" } | "OP-001" | Full text match, case-insensitive. |
| { type: "select", options } | "platform" | Exact option value, never a substring. Empty string is reserved for All. |
| { type: "select", options, searchable: true } | "platform" | Search the option labels; selecting applies its exact value. |
| { type: "number", step: 0.01 } | { operator: "eq", value: 100 } | Numeric equality: 100 never matches 1100. step defaults to any. |
| { type: "number" } | { operator: "gte", value: 0 } | Inclusive lower bound; zero is an active filter. |
| { type: "number" } | { operator: "lte", value: 100 } | Inclusive upper bound. |
| { type: "number" } | { operator: "between", value: 100, max: 500 } | Inclusive range. Either bound may be omitted. |
| { type: "date" } | { from: "2026-09-01", to: "2026-09-30" } | Inclusive calendar dates in YYYY-MM-DD; either bound may be omitted. |
| { type: "boolean", trueLabel: "Yes", falseLabel: "No" } | false | Boolean equality; false is an active filter. |
All definitions accept placeholder and label (an accessible column name for
non-text headers). Values are read from searchAccessor or the column accessor.
Date strings use their first ten characters; Date objects use the local calendar
date. Supply a date-only accessor when a business timezone differs from the
browser timezone. Null and missing values do not match active filters.
Range controls keep a draft until Apply, reject reversed bounds, and discard unapplied edits when their popover closes. Clear removes the complete column filter. Active columns combine with AND. Filters on hidden columns or columns removed from the filter row are dormant, matching the existing search model. Showing them again restores their values; Clear filters clears both legacy and typed models. Reset view restores the initial configuration.
Controlled, manual and hidden filters
const [filters, setFilters] = useState<TableFilterValues>({ approved: false });
const controlledDef: DataTableDef<Request> = {
...def,
features: {
...def.features,
search: {
mode: "row",
columnFilterValues: filters,
onColumnFilterValuesChange: setFilters,
defaultFiltersVisible: false,
strategy: "manual" // omit for local filtering
}
}
};
// With manual filtering, pass the rows returned by your API. The library emits
// the model without re-filtering that response. If the API also paginates, use
// features.pagination.strategy="manual" with its page and filtered totalItems.
// Reset the controlled page when changing the query; cancel stale API requests.defaultColumnFilterValues initializes uncontrolled filters. columnFilterValues
and onColumnFilterValuesChange form the controlled alternative. Keep existing
filterValues/onFilterValuesChange for legacy columns that have no filter.
defaultFiltersVisible initially hides or shows the row, without clearing its
values. The toolbar panel edits the same state when the row is hidden or the
viewport is small. defaultStickyFilterRow, stickyFilterRow and
onStickyFilterRowChange retain their existing controlled/uncontrolled behavior;
layout.stickyHeader is required to pin the header and filter rows together.
display.allowDensityChange adds an optional density selector. compact,
comfortable and spacious change geometry; System density restores inheritance
from the provider. The table remains horizontally scrollable on narrow screens.
title and description are optional shell heading slots. Existing toolbar
and actions slots continue to work.
Column filters compose the existing UIX controls: Input for text, Select for
categories and booleans, and FilterMenu selectionMode="single" searchable for
searchable categories. Range popovers contain Input and Select; they are not
another select implementation. The searchable panel keeps its search field above
a divider, with scrolling confined to the options against the panel edge.
features.search.filterLabels accepts any subset of TableFilterLabels:
filterLabels: {
all: "Todos", clear: "Quitar filtro", clearAll: "Limpiar filtros",
filters: "Filtros", active: n => `${n} filtros activos`,
showFilters: "Mostrar filtros", pinFilters: "Fijar filtros",
minimum: "Mínimo", maximum: "Máximo", from: "Desde", to: "Hasta",
range: "Rango…", operator: "Condición", between: "Entre", equals: "Igual a",
atLeast: "Como mínimo", atMost: "Como máximo", value: "Valor", apply: "Aplicar",
invalidRange: "El límite final debe ser igual o mayor al inicial.",
yes: "Sí", no: "No", searchOptions: "Buscar opciones", noOptions: "Sin coincidencias"
}Use the theme's semantic CSS variables for surfaces, text, borders and focus.
The implementation adds no theme-specific color literals; table geometry continues
to use --uix-table-row-height, --uix-table-header-row-height,
--uix-table-filter-row-height, --uix-table-cell-padding-block-local and
--uix-table-cell-padding-inline-local. Prefer the density API over overriding
the individual geometry variables.
Form appearances and connected actions
FormFooter keeps actions visible in a scrollable form by default. For short forms,
use <FormFooter sticky={false}>: actions stay in normal document flow without
creating an internal scroll region. Both modes use a subdued theme border; the
sticky surface is opaque so fields remain readable beneath the actions. Custom
form submission and reset handlers remain the consuming application's responsibility.
Set Form maxHeight to bound the scroll area independently of footer mode.
stickyFooterMaxHeight remains supported for existing consumers.
Embedded workspaces can set AppShell height without overriding its layout styles.
Color samples can use SwatchCard color with a theme token or CSS color; style
remains supported for compatibility. These options let consumers compose examples
without duplicating the library's CSS.
Input, Select and Textarea share state colors for their text, surfaces,
validation strokes and loading indicators. Appearance changes the contour:
underlined, outlined or inset. Valid and invalid strokes are 2px; disabled
and busy fields share a muted text color without stacking opacity. Placeholders
retain their separate hint color when the field is enabled. Text padding and loading
indicator offsets follow the same control size and density; textarea aligns its
indicator with the first text line.
Select readOnly blocks pointer and keyboard changes while preserving focus,
normal text contrast and native form submission, including multiple values.
Use disabled when the field should be unavailable and excluded from submission.
Textarea busy preserves edited text while showing the shared loading indicator.
Outlined and inset fields, including CompoundControl, use the shared 12px field
radius independently of their containing Card. Underlined fields retain rounded
top corners and a straight baseline. Embedded actions preserve the field's
density-defined height when they appear or disappear. On narrow or touch screens,
their visible controls remain inset while a minimum 44px hit target reserves
horizontal space without overlapping neighboring controls. Disabled or busy
SearchField controls cannot be cleared by the clear action or Escape.
Input, Textarea and Select support appearance="underlined" | "outlined" |
"inset"; default inherits the system appearance. Underlined focus uses a bottom
indicator and a soft halo, without a second frame around the entire field.
Outlined keeps its intentional full contour; inset retains restrained interior
depth. All three use theme tokens in light and dark modes.
Select multiple and Select nativeSize={4} render an auto-height listbox without
a dropdown chevron. Keep a visible Field label, and explain native multiple
selection when it is useful for the audience. useFormValidation focuses the
first invalid control after an unsuccessful submit. Clear each field's error when
its value becomes valid rather than keeping every error after the first submit.
ButtonGroup sizes follow the same density scale as Buttons, with a shared 12px
corner radius across sizes. Connected edge segments subtract the group border
from that radius; inset groups add their padding and border around 12px buttons
to keep the contours concentric. Connected segments stay aligned while pressed;
the selected segment uses a contrasting inset focus outline for keyboard access. Underlined groups do not add vertical separators.
Selected segments preserve UIX's upper highlight, using the same surface tokens
as solid Buttons, including hover in light and dark themes.
For asynchronous actions, pass busy={isLoading} throughout the lifecycle. The
button reserves a loading slot so the spinner and text do not overlap or shift
the button width. With leadingIcon, the spinner replaces that icon in place:
<Button busy={isLoading} leadingIcon={<RefreshCw size={16} />} onClick={reload}>
Actualizar datos
</Button>Interaction and surface shadows
Icon actions passed through an Input adornment share the field surface. Their hover and pressed feedback stays inside the field without a separate elevation shadow; keyboard focus remains visible and mobile targets remain at least 44px.
Default and outlined Cards share --uix-card-radius and --uix-card-border with
DataTableShell so adjacent data and detail panels have matching contours.
Dialog footers use a subtle neutral surface to distinguish actions from content;
override --uix-dialog-footer-surface on Dialog.Content when needed. Headers use
--uix-dialog-header-shadow for subtle separation in both themes.
DateTimePicker dialogs inherit the same header shadow and footer surface.
Buttons with busy={false} center their visible content while reserving loading
width, so the spinner can appear without resizing the button. Leading icons are
replaced in place while busy; accessible labels remain unchanged.
Buttons, CloseButton and surfaced IconButtons share resting, hover and pressed elevation. Transparent icon actions remain flat at rest. Disabled controls have no shadow; keyboard focus uses a separate visible outline. Link buttons remain flat in every state.
ButtonGroup keeps connected segments within one surface; inset groups elevate the selected segment. Selection and the upper highlight remain visible while pressing. Underlined groups retain their line indicator.
Dropdown, custom selection panels built with FilterMenu, popovers and floating
toasts share panel elevation. Dropdown, ButtonGroup menus, Popover, Tooltip,
ChipInput suggestions and permission menus use a shared frosted surface with the
provider's glassOpacity. FilterMenu enables it with panelAppearance="glass";
solid remains opaque. Reduced transparency, forced colors and browsers without
backdrop-filter support receive opaque surfaces. Menu option corners derive from
the surface radius minus padding and border. Native Select option panels are
rendered by the browser/OS and retain their native appearance. Native Select popups remain browser-managed. Dialogs, overlay drawers and SearchPalette use modal elevation.
Inline drawers, date pickers and informational cards retain their existing
surface treatment. Header and sidebar keep their directional separation.
Calendar shares Spreadsheet's outer border and radius tokens. Its compact toolbar,
date headings and category legend use a muted surface, while event cards retain
subtle edges and elevation in light and dark themes. Today uses Button appearance="reveal":
an accent line expands into a filled button on hover or keyboard focus. The same
appearance is available on any Button, with tone, loading, RTL and reduced-motion support.
Today remains a navigation action that returns to the current date.
Full-width timed events that occupy an hour expose a floating circular creation action
on hover or keyboard focus, without changing their title or time layout.
It creates a new draft at the visible segment's start and resource, without selecting
or copying the existing event. Short or crowded cards retain the action in their options
menu, as do touch devices. Read-only calendars hide creation; availability and overlap rules still apply.
Calendar events and Kanban cards gain subtle hover elevation and a lifted shadow while dragging, with the selected/focused indicator preserved.
Cards explicitly composed with role="button" or role="link" gain hover and
pressed shadows. Consumers must supply the corresponding keyboard behavior and
focusability; assigning a role alone does not make a card operable. Cards that
only contain action buttons remain informational surfaces.
The shared CSS tokens are documented in @uix-design/tokens. The Button docs
include a Sombras y estados de interacción example using public components.
Interface fonts
DesignSystemProvider fontFamily accepts a CSS font stack. useFont() exposes
fontFamily and setFontFamily. Nested providers inherit the current font unless
explicitly overridden. Set applyToDocument={false} for isolated themes. Both UI
font tokens update together; the monospace token used by code remains unchanged.
The application owns font loading, fallbacks and preference persistence.
<DesignSystemProvider fontFamily='"Manrope", system-ui, sans-serif'>
<AppHeader
title="Workspace"
fontLabel="Typography"
fontPreviewText="Plan your next project · 0123456789"
fontOptions={[
{ label: "Manrope", value: '"Manrope", system-ui, sans-serif' },
{ label: "System", value: "system-ui, sans-serif" }
]}
/>
</DesignSystemProvider>fontOptions enables the appearance picker; only list fonts that the application
loads. Documentation offers eight choices under “Personaliza tu experiencia”,
loads the selected self-hosted font with display=swap, and stores its identifier locally.
Font assets and their OFL licenses live in the docs app, outside the published UI package.
Drawer editing layout
Drawer places context, title, status and close/expand controls in one header.
Use titleAccent to add a short theme-colored marker beside the title. It defaults
to false, preserves header spacing, and also works on Drawer.Header. An explicit
Drawer.Header titleAccent={false} overrides an accented parent Drawer.
The body scrolls independently, leaving the header and footer visible. Temporary
left/right drawers use the full viewport width on screens up to 640px; inline and
persistent variants retain their composition. Padding follows density and safe areas.
| Slot | Root API | Compound API |
| ------------------------ | -------------- | ------------------------ |
| Context above the title | eyebrow | Drawer.Header eyebrow |
| Status next to the title | status | Drawer.Header status |
| Secondary header actions | actions | Drawer.Header actions |
| Footer feedback | footerStatus | Drawer.Footer status |
| Footer actions | footer | Drawer.Footer children |
Slots accept React nodes. Use a Badge for draft status, and short footer feedback
such as “Unsaved changes”. Context/status are separate from the accessible title;
footer feedback uses a polite status region. Existing title, description,
actions, footer, expansion and structured children remain supported.
onBeforeClose?: () => boolean guards built-in close, Escape and backdrop requests.
Return false to keep the drawer open and show a controlled confirmation Dialog.
On confirmation, explicitly set controlled open to false; on cancellation keep
editing. Route your own Cancel action through the same guard. External changes to
open remain authoritative, and processing continues to block dismissal.
The Drawer documentation's first demo includes editable fields, an associated footer submit button, dirty-state tracking, discard confirmation with initial focus on “Continue editing”, and functional team/history disclosures. Saving there is in-memory only; applications own persistence and save errors.
Docked and floating presentation
presentation="docked" is the default. Use presentation="floating" with a
temporary drawer to separate it from the viewport edges on desktop. This is
independent of position (right, left, top, bottom) and preserves modal
focus, dismissal guards, processing and expansion behavior. Inline and persistent
drawers ignore floating presentation so their existing layout stays intact.
<Drawer
presentation="floating"
position="right"
title="Edit route"
open={open}
onOpenChange={setOpen}
footer={<Button onClick={save}>Save changes</Button>}
>
{/* Form or details */}
</Drawer>The floating surface has symmetric outer clearance (16px, increased for device safe areas). Its top corners accommodate circular header controls; smaller bottom corners accompany footer actions. Shared spacing and geometry tokens determine these radii; density can increase content padding without inflating the corners. Header, body and footer share the same horizontal inset. The body scrolls between fixed header/footer regions, so its scrollbar does not intersect the corners. Expanded/full drawers retain outer clearance.
At widths up to 640px, floating presentation becomes docked: lateral drawers use the full width with square corners, and top/bottom sheets retain only their exposed rounded corners. Light/dark theme, runtime accent and shared elevation are inherited. The first Drawer documentation demo lets you compare both presentations using the same editable form and unsaved-change protection.
Grouped accordion
The default appearance="bordered" uses one neutral outline and quiet separators.
Expanded sections use a subtle surface change and an upward chevron; the outline
inside the header is reserved for keyboard focus. Leading icons and chevrons do
not have separate button surfaces. Existing appearances remain available.
Use description for a short collapsed summary, status for a non-interactive
badge beside the title, and actions for independent header actions. Status is
announced as a description, keeping the accessible title concise. Header actions
remain siblings of the native trigger; they never toggle the panel. With end
icons, chevrons share the same trailing alignment even when actions are present.
On small screens actions move below their heading and summaries wrap.
<Accordion
defaultValue="general"
items={[
{
value: "general",
title: "General settings",
status: <Badge>Active</Badge>,
content: <SettingsForm />
},
{
value: "automation",
title: "Automation",
description: "2 active rules",
actions: <Button onClick={openRules}>Manage</Button>,
content: <Rules />
}
]}
/>The first Accordion documentation demo includes editable settings, save/cancel feedback and an independent automation dialog. Saving is in memory for the demo; applications own persistence. Collapse keeps fields mounted but inert while hidden. Use Tab, Enter and Space with the native header buttons; no pointer-only controls or automatic focus movement are introduced.
Timeline: consistent orientations
Timeline uses the same state icons, descriptions, metadata and semantic dates in
vertical and horizontal layouts. density="compact" reduces spacing and marker
size without hiding icons or content. Use showIcons={false} for a dot-only rail;
variant="milestone" retains its distinctive diamond markers.
Dates are optional per event. A vertical timeline reserves a shared date track when any event has a visible date or time, so an undated pending event stays aligned with earlier events. If no timestamps are visible, the unused date track collapses.
orientation:vertical,horizontal, oralternating.variant:activity,progress, ormilestone.density:comfortableorcompact.connector:solid,dashed, ornone.responsive="stack"(default): horizontal timelines become vertical below a 48rem container width. Dates, descriptions and metadata remain visible.responsive="scroll": keeps horizontal steps at readable widths. The timeline is keyboard focusable so native scrolling remains available; provide anaria-labeldescribing its subject.showDate,showTime,showIcons, andshowAvatarscontrol content explicitly.selectionMode="single"selects an event without changing its actualstate.expandableexposes details with native keyboard-accessible buttons.
The current step uses aria-current="step". Supply localized statusLabel text
for assistive technology and badge for a visible status. Completed, pending and
failed steps have different icon shapes, so color is not their only distinction.
Progress connectors describe completed transitions; spacing between events does
not encode elapsed time. Calculate any overall percentage from actual domain data.
The documentation includes activity, hiring, delivery, expandable milestones and an interactive options example that compares orientations with the same dataset.
Navigation drawer edge dismissal
For mobile sidebars, dismissPlacement="edge" moves the close control to a full-height,
44px translucent, blurred inner edge on left/right drawers. The content keeps its
own surface so the dismissal rail reveals the page behind it in either theme.
A navigation surface can also use bodyLayout="flush" to remove the default body
inset and backdropAppearance="subtle" for a lighter blurred scrim. Both treatments
are native Drawer behavior, so consumers do not need wrapper classes or local styles.
A drawer with no heading or header actions
then starts directly with its content. Top/bottom drawers retain header dismissal;
inline drawers do not add a dismissal rail. Existing Escape, focus restoration,
onBeforeClose, and processing guards still apply.
<Drawer
aria-label="Navegación principal"
backdropAppearance="subtle"
bodyLayout="flush"
position="left"
dismissPlacement="edge"
dismissLabel="Cerrar navegación"
isOpen={navigationOpen}
onClose={() => setNavigationOpen(false)}
>
{navigation}
</Drawer>Tree surfaces
Tree keeps its default appearance="plain" for frameless composition. Use
appearance="panel" for a bordered surface with symmetric padding, optional
header/footer and the shared SearchField with appearance="outlined".
The panel uses the same surface-radius token as Card (24px by default). As a direct
Card child, it automatically adapts to the available corner space. Shared edges
are concentric; other corners are bounded by the Card's remaining radius. The same
contract works through responsive flow grids. Padding, rows and controls keep their sizes.
Tree does not inspect its ancestors or measure the DOM for this geometry.
<Tree appearance="panel" items={items} />
<Card title="Documentos del equipo">
<Tree appearance="panel" items={items} />
</Card>Use nestedRadius={false} to retain independent corners, or radius to fix a
length. The legacy explicit integration="standalone" and integration="inset"
values preserve their previous fixed geometry and spacing for compatibility.
Omit integration in new compositions. Plain trees remain frameless.
Use title, description and icon for the structured heading shared with the
other data workspaces. The default icon represents a hierarchy; icon={null}
hides it. headerActions places library Buttons alongside the title and wraps
below it, aligned to the start, only when the available space requires it.
The heading uses the base surface (white in the light theme); search and footer
use the muted neutral surface. A custom header replaces the structured heading.
Without an explicit accessible label, the tree uses its visible title.
<Tree
title="Carpetas del equipo"
appearance="panel"
items={items}
expandedIds={expandedIds}
onExpandedIdsChange={setExpandedIds}
headerActions={
<Button variant="primary" disabled={!expandedIds.length} onClick={() => setExpandedIds([])}>
Contraer todo
</Butto