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

Project Architecture

This document describes where things live in the Tangible UI codebase.


File Structure

src/
├── components/           # React components (co-located)
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.stories.ts
│   │   ├── styles.scss
│   │   └── index.ts
│   ├── Card/
│   ├── Chip/
│   ├── ...
│   └── index.ts          # Main barrel export
│
├── icons/                # SVG icon registry
│   ├── registry.ts       # Maps category/name → component
│   ├── system/           # Core UI icons
│   ├── cred/             # TangibleCred icons
│   ├── reaction/         # Emoji-style reactions
│   └── player/           # Media player icons
│
├── styles/
│   ├── system/           # Design tokens
│   │   ├── _colors.scss
│   │   ├── _spacing.scss
│   │   ├── _typography.scss
│   │   └── ...
│   ├── components/       # Style bundling (imports from co-located styles)
│   │   ├── _bundle.scss
│   │   └── input/        # CSS-only inputs (no React component)
│   ├── utilities/        # Layout helpers (flex, grid, spacing)
│   └── index.scss        # Main entry point
│
└── stories/              # Storybook documentation (MDX)
    ├── Welcome.mdx
    ├── Colors.mdx
    ├── Spacing.mdx
    └── ...

Component Co-location

Each component lives in its own folder with everything it needs:

File Purpose
Component.tsx React component
styles.scss Component styles (SCSS)
Component.stories.tsx Storybook stories
index.ts Barrel export

This keeps related code together and makes it easy to find everything about a component in one place.

Why Co-location?


Import Patterns

Consuming components

// From the main barrel (recommended)
import { Button, Card, Icon } from '@tangible/ui';

// Direct import (also fine)
import { Button } from '@tangible/ui/components/Button';

Within the codebase

// Same folder
import { Button } from './Button';

// Sibling component
import { Icon } from '../Icon';

// Icons registry
import { iconRegistry } from '../../icons/registry';

Styles Architecture

Token System

All design tokens use CSS custom properties with tui- prefix:

--tui-spacing-md
--tui-color-primary-base
--tui-typography-size-lg
--tui-radius-md

Tokens are defined in src/styles/system/ and emitted to .tui-interface.

Component Styles

Component styles are written in SCSS and use the token system:

@use "../../styles/system" as sys;

@mixin styles() {
  :where(.#{sys.$prefix}interface) .#{sys.$prefix}button {
    padding: var(--#{sys.$prefix}spacing-sm);
    // ...
  }
}

CSS Layers

Styles are organized into CSS layers for cascade control:

@layer defaults, components, utilities;

Build Outputs

publish/
├── index.js          # React components (ESM)
├── index.d.ts        # TypeScript declarations
├── styles/
│   └── all.css       # All styles bundled
└── ...

CSS-only Usage

For PHP-rendered HTML, include the CSS and use classes directly:

<link rel="stylesheet" href="@tangible/ui/styles/all.css">

<div class="tui-interface">
  <button class="tui-button is-theme-primary">Click me</button>
</div>

Adding a New Component

  1. Create folder: src/components/NewComponent/
  2. Add files:
    • NewComponent.tsx — React component
    • styles.scss — Styles with @mixin styles()
    • NewComponent.stories.tsx — Storybook stories
    • index.ts — Export component and types
  3. Register styles in src/styles/components/_bundle.scss
  4. Export from src/components/index.ts

Key Conventions

Convention Example
Component file Button.tsx (PascalCase)
Styles file styles.scss (always this name)
Stories file Button.stories.tsx
CSS class tui-button (kebab-case)
CSS element tui-button__icon (BEM-ish)
CSS modifier is-theme-primary, is-size-sm
CSS state is-disabled, is-active
Token --tui-spacing-md

See Naming for full class naming conventions.