Getting Started
Install
npm install @tangible/ui
Optional peer dependencies
Some components require additional packages. Install only what you use:
| Package | Required by |
|---|---|
@floating-ui/react |
Select, MultiSelect, Combobox, Dropdown, Tooltip |
@tanstack/react-table |
DataTable |
# If using Select, Dropdown, Tooltip, etc.
npm install @floating-ui/react
# If using DataTable
npm install @tanstack/react-table
Components without these dependencies (Button, Card, Accordion, Modal, Tabs, etc.) work with zero additional installs.
Import styles
In your app entry point:
import '@tangible/ui/styles';
Or if you're using SCSS and want access to tokens/mixins:
@use '@tangible/ui/styles/scss';
CSS cascade layers
TUI uses internal CSS layers (resets, defaults, components, utilities) to organise its own cascade. This works well when you control the full page.
For WordPress plugin contexts where theme CSS is unlayered, use the unlayered build — unlayered CSS always beats layered CSS regardless of specificity:
import '@tangible/ui/styles/unlayered';
| Import | Use when |
|---|---|
@tangible/ui/styles |
You control the full page |
@tangible/ui/styles/unlayered |
WordPress plugins, embedded UIs competing with theme CSS |
Wrap your app
Components require the .tui-interface wrapper to access design tokens:
function App() {
return (
<div className="tui-interface">
{/* Your UI here */}
</div>
);
}
This wrapper:
- Scopes all
--tui-*CSS custom properties - Prevents style leakage in WordPress environments
- Enables portaled content (tooltips, modals) to inherit tokens automatically
Dark mode
Set data-theme on the wrapper:
<div className="tui-interface" data-theme="dark">
| Value | Behaviour |
|---|---|
"dark" |
Force dark mode |
"auto" |
Follow prefers-color-scheme |
| (omit) | Light mode (inherits host colour) |
Use components
import { Button, Card, Select } from '@tangible/ui';
function Example() {
return (
<Card>
<Card.Body>
<Button label="Save" theme="primary" />
<Select placeholder="Choose..." onValueChange={(v) => console.log(v)}>
<Select.Trigger />
<Select.Content>
<Select.Option value="a">Option A</Select.Option>
<Select.Option value="b">Option B</Select.Option>
</Select.Content>
</Select>
</Card.Body>
</Card>
);
}
Tree-shaking
If your bundler doesn't tree-shake unused components from the barrel export, use individual imports:
import { Button } from '@tangible/ui/components/Button';
import { Tooltip } from '@tangible/ui/components/Tooltip';
Component patterns
Compound components
Several components use dot notation for their parts:
<Card>
<Card.Head>Title</Card.Head>
<Card.Body>Content</Card.Body>
<Card.Foot>Footer</Card.Foot>
</Card>
<Accordion type="single" collapsible>
<Accordion.Item value="item-1">
<Accordion.Trigger>Section 1</Accordion.Trigger>
<Accordion.Panel>Content here</Accordion.Panel>
</Accordion.Item>
</Accordion>
<Modal open={isOpen} onClose={close} aria-labelledby="title" showCloseButton>
<Modal.Head id="title">Edit Item</Modal.Head>
<Modal.Body>Content</Modal.Body>
<Modal.Foot>
<Button label="Cancel" variant="ghost" onClick={close} />
<Button label="Save" theme="primary" onClick={save} />
</Modal.Foot>
</Modal>
The asChild pattern
Some components accept asChild to merge props onto your own element instead of wrapping:
{/* Without asChild — wraps in a span */}
<Tooltip.Trigger>
<Button label="Hover" />
</Tooltip.Trigger>
{/* With asChild — props merged directly onto Button */}
<Tooltip.Trigger asChild>
<Button label="Hover" />
</Tooltip.Trigger>
Prop conventions
| Prop | Purpose | Values |
|---|---|---|
theme |
Semantic colour | primary, secondary, success, warning, danger, info |
variant |
Visual treatment | solid, outline, ghost, link (varies per component) |
size |
Component scale | sm, md, lg (varies per component) |
onValueChange |
Value callback | Used instead of onChange for non-native components |
open / defaultOpen |
Floating state | Controlled / uncontrolled for dropdowns, modals, etc. |
Customising with tokens
Token architecture
TUI uses a four-layer token system:
- Primitives (
--tui-*) — theme contract on.tui-interface(colours, spacing, radii) - Component API (
--tui-{component}-*) — never defined by TUI, only read via fallback. Set these from your code. - Internal (
--_*) — private to components, used for state management - Structural (
$_*) — SCSS-only, compile away
/* Override primitives globally */
.tui-interface {
--tui-radius-md: 2px;
--tui-focus-ring-color: hotpink;
}
/* Override a specific component's API token */
.my-context {
--tui-button-radius: 0;
--tui-accordion-padding: 8px;
--tui-modal-spacing: 24px;
}
Each component's styles.scss header lists its full token API.
Colour scale
Theme colours use a contrast-based naming scale: subtlest → subtle → soft → base → strong → stronger → strongest. "Stronger" always means more contrast against the background, making it consistent across light and dark modes.
Form control sizing
All form controls (TextInput, Select, MultiSelect, Combobox) and buttons share unified height tokens:
.my-context .tui-interface {
--tui-control-height-sm: 28px;
--tui-control-height-md: 32px;
--tui-control-height-lg: 40px;
/* Optional: decouple font size from size tier */
--tui-control-font-size-sm: 13px;
--tui-control-font-size-md: 13px;
--tui-control-font-size-lg: 14px;
}
Modal viewport offsets
Modal supports offset tokens for host chrome like the WordPress admin bar or sidebar:
.wp-admin .tui-modal {
--tui-modal-offset-top: 32px; /* admin bar */
--tui-modal-offset-start: 160px; /* sidebar */
}
Offset tokens fall back to env(safe-area-inset-*) for device notch/home indicator clearance.
Icons
Four icon sets via the registry: system, cred, reaction, player.
import { Icon } from '@tangible/ui';
<Icon name="system/check" />
<Icon name="system/settings" size="lg" />
<Icon name="reaction/clap-fill" />
Browse available icons in the Icon story.
CSS-only usage
Form inputs and some utilities work without React:
<div class="tui-interface">
<input type="text" class="tui-input" placeholder="Text input" />
<label class="tui-inline-choice">
<input type="checkbox" />
<span>Checkbox label</span>
</label>
<label class="tui-toggle">
<input type="checkbox" id="toggle-1" />
<label for="toggle-1">Toggle label</label>
</label>
</div>
See the Inputs story for all available form patterns.