Tangible Design System · @tangible/ui 0.2.29 · Interactive version · Markdown

Tangible UI class naming and CSS structure

Tangible UI aims to be predictable to work with, hard to accidentally override, and polite to the host theme. This document describes how class names are structured, how styles are scoped, and how to extend or override them safely.

Prefixes and high‑level conventions

Tangible UI uses three layers of naming to keep styles organized and collision‑free:

  1. Interface wrapper – scopes all Tangible UI styles
  2. Block / component classes – identify the UI pattern
  3. Element and modifier classes – identify sub‑parts and variations

Interface wrapper: .tui-interface

All Tangible UI components are expected to live inside a wrapper with the class .tui-interface. This wrapper is the primary guardrail against leaking styles into the rest of a site, and it also gives us a consistent way to increase specificity when we need to.

Example markup:

<div class="tui-interface">
  <div class="tui-card tui-card__body">
    ...
  </div>
</div>

Example SCSS:

.tui-interface {
  // global resets and layout helpers for Tangible UI only

  .tui-card {
    // card styles
  }
}

When writing new component styles, assume they will always be rendered inside .tui-interface and keep selectors nested under that wrapper in SCSS.

Component blocks: tui-<component>

Components are named with a tui- prefix followed by a descriptive block name:

This avoids collisions with theme classes like button, card, or table, and makes it obvious which CSS belongs to Tangible UI.

Elements and modifiers

Tangible UI loosely follows BEM-like patterns:

Patterns you will see:

Modifiers never stand alone. They always appear on top of a base block class.

<button class="tui-button is-primary is-outline">Save changes</button>

Do not create new global modifier names for one‑off cases. Prefer component‑specific modifiers when necessary, or data attributes for very custom states.

Design tokens: --tui-{category}-{axis}-{step}

Token names use one grammar, where the category is a design-language noun — not a CSS property name:

Examples: --tui-typography-weight-semibold, --tui-color-bg-muted, --tui-theme-primary-subtlest, --tui-spacing-md.

The grammar is what makes token names guessable: if you know --tui-typography-size-sm, you can derive --tui-typography-weight-medium without looking it up. The corollaries:


Wrapper and specificity strategy

Why the wrapper exists

The .tui-interface wrapper ensures:

All Tangible UI SCSS should be written in the form:

:where(.tui-interface) {
  .tui-button { ... }
  .tui-card { ... }
  .tui-table { ... }
}

Avoid targeting elements without the wrapper.

Recommended specificity

Aim to keep selectors shallow and class‑only:

:where() targets without adding specificity.

Avoid:

If overriding Tangible UI from a plugin or theme, prefer to match class‑only specificity as well. For example, to override a primary button:

.tui-interface .tui-button.is-primary {
  background-color: ...;
}

Structure of Tangible UI styles

Tangible UI SCSS is organized into several layers. The exact file layout may evolve, but the intent stays the same.

System tokens

Located under src/styles/system/:

These are the single source of truth for visual decisions. Components and utilities should depend on tokens, not hard‑coded values.

Utilities

Located under src/styles/utilities/.

Utilities are small, single‑purpose classes for layout and spacing – things like:

Components

Components are co-located in src/components/*/:

src/components/Button/
├── Button.tsx           # React component
├── Button.stories.ts    # Storybook stories
├── styles.scss          # Component styles
└── index.ts             # Barrel export

Component styles mirror the React component structure:

See Architecture for full project structure.