This is the repo-wide design rulebook for humans and AI coding agents. The compact hard rules live in AGENTS.md; this file holds the full decision trees, banned patterns, carve-outs, and rationale. The token/component catalog lives in libs/ui/AGENTS.md.
@semoss/ui (libs/ui) is the single source of truth for design.
- Tokens: libs/ui/src/styles/globals.css — semantic colors, radius, Geist fonts, type scale, heading/section utilities. Light and dark theme.
- Components:
libs/ui/src/next/, imported only as@semoss/ui/next(includingcn()). - Design reference: the Figma kit "shadcn ui kit for SEMOSS" uses the same semantic
token names, Geist, and the standard Tailwind type scale. When values differ between a
mockup and code,
globals.csswins for implementation.
Tokens and shared components make a screen theme-compatible; they do not make its composition consistent by themselves. Follow the workflow and product grammar below before choosing individual classes.
For every user-facing UI task:
- Read the owning surface. Inspect the route layout, the nearest comparable sibling, and any local composite already used by that feature. Preserve established navigation and information architecture unless the request explicitly changes them.
- Classify the surface. Choose one primary archetype from the table below. Do not mix a marketing composition into an operational screen or turn every section into a card.
- Choose from the authority order. Reuse, in order: the feature's existing shell or
composite; an
@semoss/ui/nextprimitive; a small local composite; a new shared primitive inlibs/ui/src/next/only when multiple packages need the same contract. - Define hierarchy before decoration. Identify the page title, primary task, one primary action, supporting actions, and the minimum information needed to complete the task.
- Implement the full state set. Account for initial loading, empty, populated, partial or filtered-empty, error, disabled, and submitting states where they apply.
- Check responsive and accessible behavior. Verify the layout, focus order, keyboard operation, announcements, overflow, and long content before polishing visual details.
- Run the design audit. A UI change is not complete when it merely compiles.
If a mockup conflicts with an existing feature shell, preserve the shell unless the user explicitly requests a shell redesign. An authorized redesign may change composition while preserving navigation destinations, permissions, and existing capabilities. Establish one reusable pattern rather than introducing a one-off visual dialect.
SEMOSS is an operational analytics product. Default to quiet, work-focused layouts optimized for scanning, comparison, and repeated action. Marketing-style heroes, oversized display type, decorative gradients, floating page sections, and illustration-led composition belong only on explicitly branded or promotional surfaces.
| Surface | Default composition |
|---|---|
| List, catalog, or dashboard | Page header → compact filters/actions → results summary → table or repeated collection → pagination |
| Detail or settings | Breadcrumb when needed → title/description/actions → grouped sections or tabs → sticky actions only for long forms |
| Editor or workbench | Stable application chrome → toolbar → resizable work area → contextual inspector; maximize usable canvas |
| Conversational workbench | Readable conversation → anchored composer → contextual work area; switch panes with tabs when space is limited |
| Focused task or form | Narrow readable column → grouped fields → inline validation → explicit cancel/submit actions |
| Overlay | Dialog for focused decisions, Sheet for contextual detail, Drawer for mobile-first tasks, Popover for brief anchored controls |
Use one dominant archetype per screen. A dashboard may contain cards as repeated metrics; a settings page is not a dashboard and should not wrap every field group in a card.
- Keep global navigation in the owning layout. Page components render one semantic
mainor use the shell's existingmain; never nestmainlandmarks. - Start a page with one clear
H3and optional description using text-base/leading-normal/medium with muted-foreground. Follow heading levels in order (H3sections,H4subsections); do not select a heading by visual size alone. - A standard page header contains title and optional description on the left, with task actions on the right. It stacks on narrow screens and aligns horizontally when space permits.
- Use
H1–H4,P,Small, andMutedfrom@semoss/ui/next. Reserve.heading-*for genuine branded or promotional headings, not dense application panels. - Use spacing to communicate grouping:
gap-2within a compact control group,gap-4within a section, andgap-6between major sections. Reuse the owning shell's page padding; for a new shell, start withp-4 md:p-6. - Keep prose readable with
max-w-prose; let tables, editors, charts, and work areas use the available width.
- Expose one visually primary action per page or self-contained task. Use
defaultfor that action,outlinefor supporting actions,ghostfor low-emphasis chrome, anddestructiveonly for destructive actions. - Put the primary action last in a left-to-right action group and first in the natural mobile focus sequence only when the layout preserves the same logical order.
- Use Lucide icons already available in the package. Decorative icons are
aria-hidden; icon-only buttons require an accessible name and aTooltipwhen the meaning is not universally familiar. - Use
ButtonGroupfor related buttons,ToggleGroupfor buttons that can only be toggled on or off,Tabsfor changing the display under the tab section, or menus for related choices. Do not represent a mode, boolean setting, or option set as a row of unrelated text buttons. - Keep destructive actions away from routine primary actions and require confirmation when the operation is difficult to reverse.
- Use
bg-backgroundfor the page,bg-cardfor genuinely framed objects,bg-mutedfor subdued regions, and borders orSeparatorbefore introducing shadows. - Cards are for repeated entities, summaries, or tools that need a visual boundary. Page
sections are normally unframed. Never nest cards merely to create spacing. Content cards
use
rounded-xlandp-6; compact tool rows and composer toolbars userounded-mdorrounded-lgwithp-2orp-3. Reserveshadow-smfor surfaces needing slight elevation. - Keep control density consistent within a region. Use default component sizes for primary
workflows,
smfor dense toolbars and tables, andlgonly for prominent standalone actions. - Prefer borders, spacing, and typography over decorative shadows. Do not add ornamental gradients, color blobs, or accent panels to operational screens.
- Preserve stable dimensions for toolbars, tables, grids, editors, and icon controls so loading, hover, labels, and long content do not shift surrounding layout.
- Center standalone chat in a readable column. Anchor the composer below the transcript; place attachments above its editor and compact controls below. Keep secondary actions discoverable in a menu instead of clipping controls when the column narrows.
- Group consecutive tool steps without crossing intervening assistant text. Use readable names, text statuses, and expandable inputs/results. Active groups and failures expand by default; successfully completed groups collapse unless the user chose otherwise. Reasoning starts collapsed. Pending approvals and questions remain visible even when the surrounding activity is collapsed. Expansion never executes or approves a tool.
- Keep chat and editors mounted when switching views, resizing, or maximizing a pane.
Preserve drafts, scroll positions, and unsaved edits. On narrow layouts, use shared
Tabswith only the visible pane reachable by keyboard and assistive technology. - Use the blue
primarytoken for actions;accentandsidebar-accentprovide subtle blue hover/selection surfaces in both themes. A status still uses the appropriatesuccess,warning, ordestructiverole with a textual label. - Playground defaults: an expanded 16rem navigation sidebar; a resizable 35/65 conversation/Workspace split; Chat/Workspace tabs below 64rem of available content width. Opening the Workspace temporarily collapses navigation without changing its stored preference, and closing it restores that preference. Manual navigation toggles override the temporary state. These dimensions are playground defaults, not mandates for every host.
- Loading: preserve the expected geometry. Use
Skeletonfor content-shaped loading,Spinnerinside a control or compact region, andLoadingScreenonly while an entire route or application shell is unavailable. Keep the triggering control disabled and label its in-progress action. - Empty: distinguish a genuinely empty collection from a filtered-empty result. Provide a concise title, useful description, and one relevant next action; filtered-empty states should offer a way to clear or change filters.
- Error: place an
Alertnear the failed content with a recovery action when recovery is possible. Usetoastfor transient action feedback, not as the only record of a persistent page or form error. - Success and warning: pair color and iconography with text. Status must never be conveyed by color alone.
- Forms: follow the form skill.
Use
Formand itsForm*wrappers, which compose the matching controls and Field labels, descriptions, and errors. Show validation next to its field and preserve entered values after a recoverable submit error. - Destructive actions: explain the consequence, require deliberate confirmation for difficult-to-reverse changes, and return focus to a logical control after completion.
- Write the unprefixed layout for the narrowest supported viewport, then add
sm:,md:, orlg:changes when the content needs them. Do not choose breakpoints by device name. - At 360 CSS pixels, pages must not scroll horizontally. A table, editor, canvas, or code block may scroll inside a clearly bounded and labelled region when preserving its structure is more useful than transforming it.
- Stack page headers, forms, and action groups on narrow screens; allow controls to wrap rather than shrinking labels or overflowing. Keep the primary action easy to find without changing the logical focus order between layouts. Cards, list items, buttons should stack one on top of each other.
- For data tables, deliberately choose one approach: horizontal overflow with essential columns kept visible, column prioritization, or an alternate small-screen representation. Do not automatically turn every table into cards or render two unsynchronized interaction models.
- Prefer
w-full,min-w-0,max-w-*, responsive grid tracks, andaspect-*constraints. Long identifiers and user content must wrap, truncate with an accessible full-value path, or scroll inside their own region. - Support 200% browser zoom and mobile browser chrome. Use dynamic viewport units and safe-area environment values only when building an edge-to-edge surface that needs them.
- WCAG 2.2 AA requires pointer targets at least 24 by 24 CSS pixels or sufficient spacing. Prefer the library's default 32–40 pixel controls and a 44 by 44 pixel target for primary touch interactions.
- Prefer native elements and the semantics built into
@semoss/ui/next. A visible text label normally supplies a control's accessible name; addaria-labelonly when no visible or programmatic label exists. - Maintain one logical heading hierarchy and appropriate landmarks. Do not add a
rolethat duplicates a native element. - Every operation must work with a keyboard. Preserve visible focus, visual order matching DOM
order,
Escapebehavior for dismissible overlays, initial focus, and focus return. - Associate form labels, descriptions, required state, and errors programmatically. Do not submit, navigate, or unexpectedly replace content merely because a value changed.
- Announce asynchronous loading, success, and error updates with the primitive's built-in behavior or an appropriate live region. Do not move focus for routine status updates.
- Mark decorative icons
aria-hidden. Images conveying content need meaningful alternative text; do not repeat adjacent visible text in the alternative. - Verify text, focus indicators, boundaries, and selected/error states in both light and dark themes. Never rely on color alone.
Before handing off a UI change, review the affected surface at:
| Check | Required coverage |
|---|---|
| Viewports | 360px mobile, 1440px laptop and ≥ 1440px desktop width |
| Themes | Light and dark |
| Content | Empty, typical, long strings, and dense results |
| Async states | Loading, success, and failure |
| Input | Keyboard-only operation and visible focus |
| Magnification | 200% browser zoom without lost content or controls |
Also run the relevant behavior tests and package type-check/build commands, and manually
audit full touched frontend files against this rulebook. pnpm lint:design is currently
missing from root scripts; do not report an automated design audit as passing. Record
anything that could not be exercised manually.
Need a color?
→ Use a semantic token class: bg-primary, text-muted-foreground, border-border,
text-destructive, text-success, text-warning, bg-sidebar, bg-chart-1…chart-5.
→ For text: Use text-foreground for header and subheaders, usetext-muted-foreground for descriptions.
→ Status/state colors are exactly three: destructive (errors/danger), success, warning.
→ No matching token → use the closest semantic token. Never invent a hex value or reach for
a raw palette shade (text-blue-600). If a genuinely new semantic role is needed (e.g.
info), propose adding a token to globals.css instead of styling ad hoc.
→ Single primary accent color base-primary carries every primary interactive element and primary CTA. No second brand color exists.
Need text styling?
→ Use a Typography component from @semoss/ui/next: H1–H4, Lead, P, Large,
Small, Muted, InlineCode, MultilineCode, List, Quote — or the .heading-xl/lg/md/sm
utilities for responsive marketing-style headings.
→ Inline tweaks use scale classes only (text-sm, font-medium, leading-normal).
→ Never text-[13px], text-[1.4rem], or micro-sizes like text-[9px]/text-[10px]/
text-[11px] — the smallest sanctioned size is text-xs.
→ For non-header typography components use 'text-base'.
→ Use font weights normal, medium, and bold.
Need a modal, popover, or overlay?
→ Use Dialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu,
ContextMenu, or Command from @semoss/ui/next.
→ Never hand-roll fixed inset-0 + a backdrop div + z-[N]. The lib components manage
backdrop color, stacking, focus traps, and a11y. (The repo has 9 legacy hand-rolled overlays
with four different backdrop opacities and five different z-indexes — do not add a tenth.)
Need a component?
- Import it from
@semoss/ui/next(check the inventory in libs/ui/AGENTS.md). - Reuse an existing local composite built on it.
- Build new: in
libs/ui/src/next/if reusable across packages, locally if not — always composing@semoss/ui/nextprimitives and token classes viacn(), never from raw elements + copied class strings.
Need a size or spacing?
→ Tailwind scale on an 8px rhythm: gap-2, p-4, px-6; icons via size-4/size-5
(not h-[18px] w-[18px]).
→ Arbitrary values (w-[347px], min-h-[639px], ml-[84%]) are allowed only to match an
external constraint (a third-party widget, a fixed asset). Add a reason-bearing exception
annotation on the preceding line using the format below; it is reviewed manually, not
currently processed by a design linter. Never use an unexplained disable.
- Import all shared components, hooks, and
cn()from@semoss/ui/nextonly. - Use semantic token classes for every color, border, ring, fill, and stroke.
- Tint with the token + slash-opacity idiom:
bg-primary/10,ring-ring/50,bg-destructive/20,hover:bg-accent.disabled:opacity-50on a whole element is fine. - Use Typography components (or
.heading-*) for headings and body text. - Boy-scout rule: manually review each touched/staged frontend file in full. Migrate its violations to tokens/components or apply an enumerated, reason-bearing carve-out. Do not expand cleanup into unrelated files. (Extends the root Incremental Migration policy.)
| Banned pattern | Example (wrong) | Instead |
|---|---|---|
Arbitrary hex classes (-[#) |
bg-[#f0f0f0], text-[#808080], border-[#E6E6E6] |
bg-muted, text-muted-foreground, border-border |
| Raw Tailwind palette classes | text-green-600, bg-gray-500, border-blue-500/40 |
text-success, bg-muted, border-primary/40 |
| Pixel/rem font sizes | text-[11px], text-[1.4rem] |
text-xs, H2 component |
| Arbitrary z-index | z-[100], z-[1300], z-[1501] |
Use lib overlay components; they manage stacking |
| Opacity on raw colors | bg-white/15, text-black/50, text-yellow-800/80 |
bg-background/50, text-muted-foreground, text-warning |
| Inline style colors/fonts | style={{ color: "#999" }}, style={{ fontSize: 12 }} |
Token classes (see carve-outs for runtime values) |
Importing libs/ui/src/next/theme.ts |
import { lightTheme } from ... |
Dead MUI-shaped palette; contradicts globals.css. Never use |
| MUI / Emotion | @mui/*, @emotion/* |
Fully removed from the repo; never reintroduce |
| Copying lib class strings onto raw elements | pasting textarea.tsx classes onto a <textarea> |
Render the component: <Textarea /> |
Bare @semoss/ui barrel imports |
import { Button } from "@semoss/ui" |
import { Button } from "@semoss/ui/next" |
| Re-implementing lib components | local tooltip/modal/button forks | Import from @semoss/ui/next |
| Ad-hoc per-file status color maps | BADGE_TONES = { blue: "bg-blue-500/10 ..." } |
Badge variants + destructive/success/warning/chart-* tokens |
- Token definitions: literal colors, radii, and fonts are allowed in
libs/ui/src/styles/globals.css, which defines the system. Components consuming those values are not exempt. - User-configurable canvas data: values rendered by
libs/renderer/src/components/block-defaults/**/*Block.tsxand equivalent Blocks workspace previews may use runtime inline styles. Mark each site with a reason-bearinginline-visual-stylesuppression containinguser-configured; the renderer's own chrome follows all rules. - ECharts: canvas option objects may require resolved color strings. Read chart tokens
through one shared adapter and reuse its
chart-1…chart-5values; never free-hand a literal map in a feature file. - Third-party style bridges: adapter CSS such as FlexLayout may map the library's custom properties to SEMOSS semantic variables. A literal required by an external API needs a line-level suppression naming that constraint.
- Host-controlled extension UI: VS Code webviews should use
--vscode-*host variables, including documented fallbacks where required. Chrome extension CSS cannot consume the UI package directly; new literals must match a currentglobals.csstoken and carry a line-level suppression naming that token.
Exception annotation format (manual review; no current automated suppression processor):
// design-lint-disable-next-line arbitrary-size -- fixed dimensions of vendor canvasMarkdown fences that intentionally demonstrate invalid code must include
design-lint-ignore in the fence info string. This documents intent for reviewers,
not a currently enforced directive. Do not suppress positive examples.
packages/browser-automation/src/index.css@themeblock (--color-canvas,--color-surface,--color-accent: #36c7b0, …): a rival, dark-only palette. Deprecated — do not extend it, do not add tokens, do not diverge its values further fromglobals.css. When touching a file that uses it, migrate those usages to@semoss/uisemantic classes.libs/ui/src/next/theme.ts: dead MUI-shaped palette with wrong values (#1976d2vs the source inglobals.css). Zero consumers. Never import; deletion is future work.
- Use
libs/ui/src/next/to learn a primitive's API, variants, slots, and accessibility behavior. Primitive implementation details are not page-composition templates. packages/client/src/components/ui/section/section.tsxdemonstrates a small local composite built withcn()and semantic token classes.- Search the owning feature for its current route shell, page header, and state handling before borrowing a pattern from another package. No application file is a blanket exemption from this rulebook.
Manually audit complete touched/staged frontend files before marking work complete. Every violation must be migrated in scope or covered by an enumerated, reason-bearing carve-out above. Include responsive, accessibility, state, and theme checks from the definition of done, and report unavailable verification.
pnpm lint:design is currently missing from root scripts. The explicit Biome checks
described in the React standard do not enforce this
design rulebook; neither a successful lint check nor a build replaces manual design review.
- Implement a scoped design checker and its command/hook integration; do not claim automated design enforcement or working suppression directives until verified.
--infotoken, if a real need appears (propose before styling).- A z-index scale token set; today, rely on lib overlay components.
- Delete
libs/ui/src/next/theme.ts. - Migrate
packages/browser-automationoff its local@themeblock.