Files
silo/docs/STYLE.md
Forbes 2585305590 fix(web): standardize button borderRadius, fontSize, fontWeight (#68)
All button style objects now use:
- borderRadius: 0.375rem
- fontSize: 0.75rem
- fontWeight: 500

Files: CreateItemPane, EditItemPane, DeleteItemPane, BOMTab,
CategoryPicker, ProjectsPage, SchemasPage, LoginPage,
ItemsToolbar, SettingsPage, ImportItemsPane, ItemTable

Closes #68
2026-02-13 13:21:54 -06:00

16 KiB
Raw Permalink Blame History

Silo Style Guide

Living reference for the Silo web UI. All modules must follow these conventions to maintain visual consistency across the platform.


Color System

Silo uses the Catppuccin Mocha palette exclusively. All colors are referenced via CSS custom properties defined at :root.

Palette

--ctp-rosewater:  #f5e0dc
--ctp-flamingo:   #f2cdcd
--ctp-pink:       #f5c2e7
--ctp-mauve:      #cba6f7
--ctp-red:        #f38ba8
--ctp-maroon:     #eba0ac
--ctp-peach:      #fab387
--ctp-yellow:     #f9e2af
--ctp-green:      #a6e3a1
--ctp-teal:       #94e2d5
--ctp-sky:        #89dceb
--ctp-sapphire:   #74c7ec
--ctp-blue:       #89b4fa
--ctp-lavender:   #b4befe
--ctp-text:       #cdd6f4
--ctp-subtext1:   #bac2de
--ctp-subtext0:   #a6adc8
--ctp-overlay2:   #9399b2
--ctp-overlay1:   #7f849c
--ctp-overlay0:   #6c7086
--ctp-surface2:   #585b70
--ctp-surface1:   #45475a
--ctp-surface0:   #313244
--ctp-base:       #1e1e2e
--ctp-mantle:     #181825
--ctp-crust:      #11111b

Semantic Roles

Role Token Usage
Page background --ctp-base Main content area
Panel background --ctp-mantle Sidebars, detail panes, headers
Inset/input background --ctp-crust Form inputs, code blocks, drop zones
Primary accent --ctp-mauve Primary buttons, active states, links, selection highlights
Secondary accent --ctp-blue Informational highlights, secondary actions
Success --ctp-green Confirmations, positive status
Warning --ctp-yellow Caution states, pending actions
Danger --ctp-red Destructive actions, errors, required indicators
Informational --ctp-teal Auto-generated metadata, system-assigned values
Body text --ctp-text Primary content
Secondary text --ctp-subtext1 Descriptions, timestamps
Muted text --ctp-overlay1 Placeholders, disabled states
Borders --ctp-surface0 Dividers, panel edges
Hover borders --ctp-surface1 Interactive element borders, row separators
Focus ring rgba(203, 166, 247, 0.25) box-shadow on focused inputs (mauve at 25%)

Accent Usage for Data Types

Data type Color Token
Assembly --ctp-mauve Badge, icon tint
Part --ctp-green Badge, icon tint
Document --ctp-blue Badge, icon tint
Purchased --ctp-peach Badge, icon tint
Phantom --ctp-overlay1 Badge, icon tint

These mappings are used anywhere item types appear: list badges, detail pane headers, BOM entries, tree views.


Typography

Scale

Role Size Weight Token/Color Transform
Page title 1.1rem 600 --ctp-text None
Section header 11px 600 --ctp-overlay0 Uppercase, letter-spacing: 0.06em
Form label 11px 600 --ctp-overlay1 Uppercase, letter-spacing: 0.05em
Body text 13px 400 --ctp-text None
Table cell 12px 400 --ctp-text None
Caption / metadata 11px 400 --ctp-subtext0 None
Badge text 10px 600 Varies Uppercase
Breadcrumb segment 13px 500 --ctp-subtext1 None
Breadcrumb active 13px 600 --ctp-text None

Font Stack

font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;

No external font dependencies. System fonts ensure fast rendering and native feel across platforms.

Rules

  • Never use font sizes below 10px.
  • Use font-weight: 600 for emphasis instead of bold (700). Reserve 700 for page titles only when extra weight is needed.
  • text-transform: uppercase is reserved for section headers, form labels, and badges. Never uppercase body text or descriptions.

Spacing

Base unit: 4px. All spacing values are multiples of 4.

Token Value Usage
xs 4px (0.25rem) Tight gaps: icon-to-label, tag internal padding
sm 8px (0.5rem) Compact spacing: between related fields, badge padding
md 12px (0.75rem) Standard: form group gaps, sidebar section padding
lg 16px (1rem) Section separation, card padding
xl 24px (1.5rem) Page-level padding, major section breaks
2xl 32px (2rem) Page horizontal padding

Application

  • Page padding: 1.5rem 2rem (24px vertical, 32px horizontal)
  • Sidebar section padding: 1rem 1.25rem
  • Form grid gap: 1.25rem 1.5rem (row gap × column gap)
  • Table row height: 36px minimum (padding included)
  • Table cell padding: 0.4rem 0.75rem

Layout

Page Structure

Every module page follows the same shell:

┌─────────────────────────────────────────────────┐
│ Top Nav (52px)                                  │
├──────────┬──────────────────────────────────────┤
│ App Menu │ Page Header (58px)                   │
│ (icons)  ├──────────────────────┬───────────────┤
│          │ Content Area         │ Detail Pane   │
│          │                      │ (360px)       │
│          │                      │               │
│          │                      │               │
└──────────┴──────────────────────┴───────────────┘
  • Top nav: 52px height, --ctp-mantle background, 1px solid --ctp-surface0 bottom border.
  • App menu sidebar: Icon strip on the left. Module icons, tooltips on hover. Active module highlighted with --ctp-mauve indicator.
  • Page header: 58px height, --ctp-mantle background. Contains page title (with module icon), action buttons right-aligned.
  • Content area: --ctp-base background. Scrollable. Contains list views, kanban boards, or other primary content.
  • Detail pane: 360px fixed width, --ctp-mantle background, 1px solid --ctp-surface0 left border. Appears on record selection.

Grid Patterns

Two-column form:

display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.25rem 1.5rem;
max-width: 800px;

List + detail:

display: grid;
grid-template-columns: 1fr 360px;
min-height: calc(100vh - 52px - 58px);

Breakpoints

Not currently required. Silo targets desktop browsers on engineering workstations. If mobile support is added later, breakpoints will be defined at 768px and 1024px.


Components

Buttons

Four tiers. All buttons share a base style:

display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.4rem 0.85rem;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
Tier Name Background Border Text Hover
Primary .btn-primary --ctp-mauve --ctp-mauve --ctp-crust --ctp-lavender bg + border
Secondary .btn (default) --ctp-surface0 --ctp-surface1 --ctp-text --ctp-surface1 bg, --ctp-overlay0 border
Ghost .btn-ghost transparent transparent --ctp-subtext0 --ctp-surface0 bg, --ctp-text text
Danger .btn-danger transparent --ctp-surface1 --ctp-red rgba(243, 139, 168, 0.1) bg, --ctp-red border

Primary is used once per visible context (the main action). All other actions use secondary or ghost. Danger is only for destructive actions and always requires confirmation.

Badges

Used for type indicators, status labels, and tags.

display: inline-flex;
align-items: center;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;

Badges use a translucent background derived from their accent color:

/* Example: assembly badge */
background: rgba(203, 166, 247, 0.15);  /* --ctp-mauve at 15% */
color: var(--ctp-mauve);

Standard badge colors follow the accent usage table. Status badges:

Status Color
Active / Released --ctp-green
Draft / In Progress --ctp-blue
Review / Pending --ctp-yellow
Obsolete / Rejected --ctp-red
Locked --ctp-overlay1

Form Inputs

All inputs share a base style:

background: var(--ctp-crust);
border: 1px solid var(--ctp-surface1);
border-radius: 6px;
padding: 0.45rem 0.65rem;
font-size: 12px;
color: var(--ctp-text);
transition: border-color 0.15s;
State Border Shadow
Default --ctp-surface1 None
Hover --ctp-overlay0 None
Focus --ctp-mauve 0 0 0 0.2rem rgba(203, 166, 247, 0.25)
Error --ctp-red 0 0 0 0.2rem rgba(243, 139, 168, 0.15)
Disabled --ctp-surface0 None, opacity: 0.5

Placeholder text: --ctp-overlay0. Labels sit above inputs (never inline or floating).

Tag Input

Used for multi-value fields (projects, tags):

display: flex;
flex-wrap: wrap;
gap: 0.3rem;
padding: 0.35rem 0.5rem;
background: var(--ctp-crust);
border: 1px solid var(--ctp-surface1);
border-radius: 6px;
min-height: 36px;

Individual tags use the badge pattern: rgba(accent, 0.15) background with accent text. Remove button (×) at opacity: 0.6, 1.0 on hover.

Tables

width: 100%;
border-collapse: collapse;
font-size: 12px;
Element Style
Header row background: --ctp-mantle, font-size: 11px, uppercase, --ctp-overlay1 text
Body row border-bottom: 1px solid --ctp-surface0
Row hover background: --ctp-surface0
Row selected background: rgba(203, 166, 247, 0.08)
Cell padding 0.4rem 0.75rem
Text columns Left-aligned
Number columns Right-aligned
Date columns Right-aligned
Action columns Center-aligned

Row actions use icon buttons (not text links). Icons at 14px, --ctp-overlay1 default, --ctp-text on hover.

Tabs

Used in detail panes and module sub-views:

display: flex;
gap: 0;
border-bottom: 2px solid var(--ctp-surface0);
State Style
Default padding: 0.5rem 1rem, --ctp-subtext0 text, no border
Hover --ctp-text text
Active --ctp-text text, font-weight: 600, border-bottom: 2px solid --ctp-mauve (overlaps container border)

Section Dividers

Used to visually group form fields:

display: flex;
align-items: center;
gap: 0.75rem;
grid-column: 1 / -1;   /* span full form grid */
margin-top: 0.75rem;

Contains a label (11px, uppercase, --ctp-overlay0) and a horizontal line (flex: 1, 1px solid --ctp-surface0).

Sidebar Sections

Stacked vertically within detail panes:

padding: 1rem 1.25rem;
border-bottom: 1px solid var(--ctp-surface0);

Last section has no bottom border. Section titles follow the section header typography (11px, uppercase, --ctp-overlay0).

Tooltips

Appear on hover after a 300ms delay. Position: above the target element by default, flip below if insufficient space.

background: var(--ctp-surface0);
border: 1px solid var(--ctp-surface1);
border-radius: 4px;
padding: 0.3rem 0.6rem;
font-size: 11px;
color: var(--ctp-text);
box-shadow: 0 4px 12px rgba(17, 17, 27, 0.4);

Breadcrumbs

Module navigation breadcrumbs:

Module Name  >  List View  >  Record Name  >  Sub-view

Separator: > character in --ctp-overlay0. Segments are clickable links in --ctp-subtext1. Active (final) segment is --ctp-text at font-weight: 600.

Dropdowns / Selects

Follow the input base style. The dropdown menu:

background: var(--ctp-surface0);
border: 1px solid var(--ctp-surface1);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(17, 17, 27, 0.5);
padding: 0.25rem;
max-height: 240px;
overflow-y: auto;

Menu items:

padding: 0.4rem 0.65rem;
border-radius: 4px;
font-size: 12px;
color: var(--ctp-text);
cursor: pointer;

Hover: background: --ctp-surface1. Selected: background: rgba(203, 166, 247, 0.12), color: --ctp-mauve, font-weight: 600.


Icons

Use Lucide icons. Size: 14px for inline/table contexts, 16px for buttons and navigation, 20px for page headers and empty states.

Stroke width: 1.5px (Lucide default). Color inherits from parent text color unless explicitly set.

Do not mix icon libraries. If Lucide does not have a suitable icon, request one be added or create a custom SVG following Lucide's 24×24 grid and stroke conventions.


Transitions & Animation

All interactive state changes use transition: all 0.15s ease. This applies to hover, focus, active, and open/close states.

No entrance animations on page load. Content renders immediately. Skeleton loaders are acceptable for async data using a pulsing --ctp-surface0--ctp-surface1 gradient.

Dropdown menus and tooltips appear instantly (no slide/fade). Collapse/expand panels (if used) transition max-height at 0.2s ease.


Styling Implementation

Silo's React frontend uses inline React.CSSProperties objects with var(--ctp-*) token references. This is the project convention and must not be changed.

Rules

  • No CSS modules, no Tailwind, no external CSS-in-JS libraries.
  • Styles are defined as const objects at the top of each component file.
  • Shared style patterns (button base, input base) can be extracted to a styles/ directory as exported CSSProperties objects.
  • Use as const or as React.CSSProperties for type safety.
  • Pseudo-classes (:hover, :focus) require state-driven inline styles or a thin CSS file for the base pseudo-class rules.

Example

const styles = {
  container: {
    display: 'grid',
    gridTemplateColumns: '1fr 360px',
    height: '100%',
    overflow: 'hidden',
  } as React.CSSProperties,

  sidebar: {
    background: 'var(--ctp-mantle)',
    borderLeft: '1px solid var(--ctp-surface0)',
    display: 'flex',
    flexDirection: 'column' as const,
    overflowY: 'auto' as const,
  } as React.CSSProperties,
};

Pseudo-class CSS

A single silo-base.css file provides pseudo-class rules that cannot be expressed inline:

/* Hover, focus, and active states for core interactive elements */
.silo-input:hover { border-color: var(--ctp-overlay0); }
.silo-input:focus { border-color: var(--ctp-mauve); box-shadow: 0 0 0 0.2rem rgba(203, 166, 247, 0.25); }
.silo-btn:hover { /* per-tier overrides */ }
.silo-row:hover { background: var(--ctp-surface0); }

Components apply the corresponding class names alongside their inline styles. This is the only place class-based styling is used.


Do / Don't

Do Don't
Use var(--ctp-*) for every color Hardcode hex values
Use the 4px spacing scale Use arbitrary padding/margins
Use Lucide icons at standard sizes Mix icon libraries
Use inline CSSProperties Use CSS modules or Tailwind
One primary button per visible context Multiple competing primary buttons
Use translucent accent backgrounds for badges Use solid bright backgrounds for badges
Use icon buttons for row-level table actions Use text links in table rows
Define styles as const at file top Inline style objects in JSX
Show tooltips on icon-only buttons Leave icon buttons unlabeled
Use section dividers to group form fields Use cards or borders around field groups
Follow the breadcrumb pattern for navigation Use nested tab bars

Appendix: CSS Custom Properties Block

Paste this at the root of the application stylesheet:

:root {
  --ctp-rosewater: #f5e0dc;
  --ctp-flamingo: #f2cdcd;
  --ctp-pink: #f5c2e7;
  --ctp-mauve: #cba6f7;
  --ctp-red: #f38ba8;
  --ctp-maroon: #eba0ac;
  --ctp-peach: #fab387;
  --ctp-yellow: #f9e2af;
  --ctp-green: #a6e3a1;
  --ctp-teal: #94e2d5;
  --ctp-sky: #89dceb;
  --ctp-sapphire: #74c7ec;
  --ctp-blue: #89b4fa;
  --ctp-lavender: #b4befe;
  --ctp-text: #cdd6f4;
  --ctp-subtext1: #bac2de;
  --ctp-subtext0: #a6adc8;
  --ctp-overlay2: #9399b2;
  --ctp-overlay1: #7f849c;
  --ctp-overlay0: #6c7086;
  --ctp-surface2: #585b70;
  --ctp-surface1: #45475a;
  --ctp-surface0: #313244;
  --ctp-base: #1e1e2e;
  --ctp-mantle: #181825;
  --ctp-crust: #11111b;
}