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

@uix-design/react

v0.13.0

Published

React components, theme provider, forms, navigation, overlays, and data display primitives for UIX Design.

Downloads

1,217

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. Respect signal to abort the request. Report actual file bytes through onProgress if 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. Without onUpload, files stay ready for a form; read their File references from onFilesChange(items). Queue ownership stays in the component. Remount with a different React key when starting a different form/session.
  • accept, maxFileSize (50 MiB default), maxFiles (10 retained files default), multiple and validateFile apply equally to selection and drop. Duplicates are compared by name, size, MIME type and last-modified metadata without hashing file contents. onReject receives 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. disabled blocks 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 maxFiles for 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; locale formats byte counts. Native buttons support keyboard operation, status updates use a polite live region, and percentages do not continually interrupt screen readers. radius and nestedRadius share 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/tokens

Styles 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 through appearance. 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. Use edgeCorners="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, or alternating.
  • variant: activity, progress, or milestone.
  • density: comfortable or compact.
  • connector: solid, dashed, or none.
  • 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 an aria-label describing its subject.
  • showDate, showTime, showIcons, and showAvatars control content explicitly.
  • selectionMode="single" selects an event without changing its actual state. expandable exposes 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