Sensible UI

v1.6.0beta

A semantic-first CSS component library with shadcn-style visual defaults.

Getting started

Import the complete stylesheet when you want every semantic default and component:

Or use the versioned CDN build:

Standalone component imports include the theme and base styles they need. When markup combines components, import each component. For example, <table class="card"> needs both /table and /card.

All public stylesheet exports
ExportPurpose
., /cssComplete bundled stylesheet
/minMinified complete bundle
/indexSource CSS entry point
/baseTheme tokens and semantic reset
/themeTheme tokens and cascade layer order
/accordion, /badge, /button, /cardStandalone component modules
/code/cssOptional code component styles; JavaScript is under /code
/description-list, /image, /input, /itemStandalone component modules
/spinner, /table, /typographyStandalone component modules
/utilsNamed layouts, container, and composition helpers
/utilities, /utilities/minOptional generated atomic utilities
/view-transitionOptional same-origin page transitions

Limit styles to part of a page

Use the scoped bundle when adding Sensible UI to an existing application. Semantic defaults apply only inside a neutral .sensible-ui wrapper, leaving the rest of the page alone.

Put the class on a wrapper, not on a card, table, link, or form control. Sensible UI layout and utility classes such as .stack, .mt-4, and .size-8 also belong on descendants, not the scope root. You can combine the scope class with a host-owned wrapper class. Scoped component and utility imports use the same names under /scoped, such as /scoped/button and /scoped/utilities. Scoped mode requires browser support for @scope.

View the scoped bundle beside host styles.

Typography

Standalone import: @import '@faith-tools/sensible-ui/typography';

Headings, paragraphs, links, lists, code, keyboard input, quotations, and inline text semantics are styled without classes. Keep the native element that matches the content’s meaning.

Geist and Geist Mono are optional. The stylesheet prefers them but does not load them, so your system fonts are used unless you provide the fonts separately. See the font setup in the README for HTML and Next.js examples, or override --font-sans and --font-mono to use your own fonts.

A clear heading

Use semantic HTML for meaning and emphasis.

Good defaults should support the content.

Press Ctrl + K to search.

const greeting = "Hello";

Add highlighted code

If your project uses a bundler, import the optional stylesheet and browser module. The default Sensible UI stylesheet does not include either one. If you have not installed the package, run npm install @faith-tools/sensible-ui first.

  1. Import the component styles in your CSS entry:

  2. Import the component in your browser JavaScript entry:

  3. Add sensible-code with a read-only textarea containing the source:

The code below shows syntax colors and a Copy button. Without JavaScript, the read-only text area remains readable. The component creates pre and code when it loads. In HTML source, escape & before entity names. If the example contains </textarea>, write its opening angle bracket as &lt;. Set language to a supported language name, such as html, css, or python.

Buttons

Standalone import: @import '@faith-tools/sensible-ui/button';

Native buttons, button-like inputs, and a.button are supported. Use data-variant for primary, secondary, outline, ghost, link, or destructive treatment. Use data-size for sm, lg, or icon. Native disabled, aria-pressed, aria-busy, and aria-invalid attributes style state.

Use a button for actions and a link for navigation. Give icon-only buttons an accessible name.

Link as button

Form controls

Standalone import: @import '@faith-tools/sensible-ui/input';

The input module styles native controls and their labels. Associate every control with a label. Use fieldset andlegend for related choices, and use aria-describedby when an error or hint needs to be announced.

Text-like inputs and states

Text, email, password, number, search, URL, and similar inputs share the same semantic default.

Enter a valid email address.

Textarea

Select

Use the native select when choosing one option from a list. Group long option lists with optgroup.

Checkbox

Use checkboxes for independent choices. The checked and disabled states are native.

Notifications

Radio group

Radio buttons with the same name represent one choice. Wrap the group in a fieldset with a legend.

Contact preference

Switch

Add role="switch" to a checkbox only when the control immediately turns a setting on or off.

Range

Provide a visible label and meaningful minimum, maximum, and initial values.

Date and time

Date and datetime-local inputs preserve each browser’s native picker and keyboard behavior.

Color

File

Use the accept attribute as a picker hint, not as file validation.

Card

Standalone import: @import '@faith-tools/sensible-ui/card';

Add class="card" to a semantic container. Direct header, section, and footer children define its regions. Add data-slot="card-action" to a header action. Cards adapt their layout through container queries.

Team plan

For growing organizations

Invite collaborators and share project settings.

Badge

Standalone import: @import '@faith-tools/sensible-ui/badge';

Add class="badge" to short status or category text. Supported variants are primary, secondary, outline, and destructive. Use a link only when the badge navigates somewhere. aria-invalid="true" provides the invalid state.

Default Secondary Outline Destructive Linked badge

Image

Standalone import: @import '@faith-tools/sensible-ui/image';

Images receive responsive sizing and rounded corners. Use an empty alt value for decorative images and useful alternative text for informative images. Pair an image and caption with figure and figcaption.

A mountain ridge beneath a cloudy sky
Images and captions receive sensible defaults.

Item

Standalone import: @import '@faith-tools/sensible-ui/item';

An item is a compact content row with an optional icon or action. Use a.item when the entire row navigates. Do not put another interactive control inside a linked item.

Project settings

Manage members, billing, and notifications.

Linked item

The whole row is one descriptive link.

Loading spinner

Standalone import: @import '@faith-tools/sensible-ui/spinner';

Add aria-busy="true" while an element is updating. Add data-variant="overlay" to dim existing children. Keep visible loading text or an accessible name so the state is understandable without relying on motion.

Loading report

The current content remains visible while loading.

Accordion

Standalone import: @import '@faith-tools/sensible-ui/accordion';

Native details and summary provide disclosure behavior without JavaScript. The open attribute sets the initial state. Give related details elements the same name when only one should remain open.

Does this require JavaScript?

No. It uses the native details and summary elements.

Can only one item stay open?

Yes. Give related details elements the same name.

Description list

Standalone import: @import '@faith-tools/sensible-ui/description-list';

Use a description list for name-value groups, terms and definitions, or metadata. Multiple terms may share a description and one term may have multiple descriptions. Add class="card" and import the card module for the bordered treatment.

Status
Active
Plan
Team
Renewal date

Table

Standalone import: @import '@faith-tools/sensible-ui/table';

Use tables for two-dimensional data. Add a caption when the surrounding context does not already identify the table, and usescope on row and column headers. Add class="card" and import the card module for the bordered treatment.

Current project members
Name Role Status
Margaret Nguyen Owner Active
Hoshi Nakamura Editor Invited
Total2

Named layouts

Standalone import: @import '@faith-tools/sensible-ui/utils';

The core bundle includes .stack for vertical flow, .cluster for wrapping inline groups, .split for separated content, and .auto-grid for intrinsic grids. .y-stack aliases stack and .x-stack is a non-wrapping inline stack. Set --layout-gap to adjust spacing and --min-item-size to control grid wrapping.

Cluster Wraps inline content
Split layout
First
Second
Third

Optional atomic utilities

Optional import: @import '@faith-tools/sensible-ui/utilities';

The companion stylesheet provides token-backed display, flex, grid, alignment, sizing, spacing, positioning, overflow, text, aspect-ratio, and accessibility helpers. It is plain generated CSS and requires no template scanning or consumer-side tooling. Override the --space-* custom properties to change its spacing scale. Prefer named helpers such as .stack, .y-stack, and .x-stack for common composition, then use atomic helpers for exceptions. Breakpoint-prefixed variants are intentionally not generated; use intrinsic layouts or consumer-owned media queries instead.

1:1
Status: Utility example

A long status message is truncated with an ellipsis when the available inline space is limited, preserving a compact row without wrapping into the content below or pushing neighboring controls out of view.

Dark mode

Add class="dark" to an ancestor to select the bundled dark theme. Components use the same markup in both themes. System preference behavior is not enabled by the core bundle.

Dark card

The same semantic markup works.

Dark theme