Design system

This page is a living reference for the visual and structural primitives used across the site. Each primitive is shown here with every variant it supports. Treat this page as the canonical source of truth — if a primitive renders differently elsewhere, that is a bug.

Spacing tokens

Semantic role tokens (canonical)

Use these tokens in all new code. Each token encodes a role, not a raw size.

TokenValueSwatchRole
--space-none0Explicit zero gap — use instead of a 0 literal wherever a gap or padding should be absent.
--space-tight0.5rem
Inside a single control (icon-to-label, badge padding)
--space-group0.75rem
Between closely related items (buttons in a row, inline fields)
--space-form-row1rem
Between form rows, list items, stacked labeled fields
--space-block1.25rem
Between distinct content blocks inside a section
--space-section2.5rem
Between major page sections

Legacy pixel scale (deprecated — do not use in new code)

These tokens exist for backward compatibility only. Do not use them in new code; migrate call sites to the semantic role tokens above when you touch them.

TokenValue
--space-10.25rem
--space-20.5rem
--space-30.75rem
--space-41rem
--space-51.25rem
--space-61.75rem
--space-72.5rem
--space-83.5rem
--space-95rem

Vertical rhythm

site-main-inner owns section-to-section spacing. It is a flex column with gap: var(--space-section) (2.5 rem), so every direct child is separated by the same section gap without any child needing to set a margin.

Nested containers such as rules-section and layout-stack own the spacing between their own children, using smaller tokens: --space-block (1.25 rem) for content blocks inside a section, or --space-form-row (1 rem) for stacked form rows.

Components should not set margin-top or margin-bottom on themselves. Let the parent gap provide the spacing. Doing otherwise produces compounding or unpredictable gaps.

DOM-tree example

site-main-inner            ← gap: --space-section (2.5rem between children) ├─ h1                    ← no margin; gap above it comes from site-main-inner ├─ rules-lead            ← no margin; gap above it comes from site-main-inner └─ rules-section         ← no margin; gap above it comes from site-main-inner ├─ h2               ← no margin; rules-section owns inner spacing ├─ p                ← no margin; rules-section owns inner spacing └─ rule-callout     ← no margin; rules-section owns inner spacing

Exception: hr keeps margin: var(--space-7) 0. It is used as an in-flow visual divider inside prose-like containers (such as this design-system page), not as a container boundary, so symmetric margin is intentional.


Layout primitives

Four CSS-only Light-DOM custom elements that compose the semantic spacing tokens. No JavaScript. No Shadow DOM. Styled via attribute selectors on unknown HTML tags.

<layout-stack>

Stacks children vertically in a flex column; use it wherever content flows top-to-bottom with consistent spacing between items.

AttributeValuesDefaultNotes
gapnone | tight | group | form-row | block | sectionform-rowMaps to the corresponding --space-* token.

Live examples

gap="none"

Item A Item B Item C

gap="tight"

Item A Item B Item C

gap="group"

Item A Item B Item C

gap="form-row" (default)

Item A Item B Item C

gap="block"

Item A Item B Item C

gap="section"

Item A Item B Item C

<layout-row>

Arranges children horizontally in a wrapping flex row with vertically centered alignment; use it for button groups, inline fields, and any items that belong side by side.

AttributeValuesDefaultNotes
gapnone | tight | group | form-row | block | sectiongroupMaps to the corresponding --space-* token.

Live examples

gap="none"

Item A Item B Item C

gap="tight"

Item A Item B Item C

gap="group" (default)

Item A Item B Item C

gap="form-row"

Item A Item B Item C

gap="block"

Item A Item B Item C

gap="section"

Item A Item B Item C

Wrap example — many items

One Two Three Four Five Six Seven

<layout-grid>

Places children into a responsive auto-fit grid where each column is at least min wide, growing to fill available space; use it for card grids, stat blocks, and any content where columns should reflow automatically.

AttributeValuesDefaultNotes
gapnone | tight | group | form-row | block | sectionblockMaps to the corresponding --space-* token.
minsm | md | lgmd (16rem)sm = 12rem, md = 16rem, lg = 20rem. Resolves via --grid-min-* tokens.

Live examples

Default (gridmin="medium", gap="block")

Card A Card B Card C Card D

gridmin="small" — tighter columns (12rem min)

Card A Card B Card C Card D Card E Card F

gridmin="large"— wider columns (20rem min)

Card A Card B Card C

<layout-between>

Places children in a horizontal row with space pushed between them, useful for headers, toolbars, and any pairing of a label on one side with an action on the other.

AttributeValuesDefaultNotes
gapnone | tight | group | form-row | block | sectiongroupMaps to the corresponding --space-* token. Gap applies when items wrap on narrow viewports.

Live examples

Two children

Left Right

Three children

Left Centre Right

gap="none"

Left Right

gap="block"

Left Right

Surface

Any container can be annotated with surface to pick up a shared visual treatment — background, border, and radius. Works on any element.

AttributeValuesDefaultNotes
surfaceraised | flat | sunken(none — absent = no treatment)Any element

Live examples

surface="raised"

Child A Child B

surface="flat"

Child A Child B

surface="sunken"

Child A Child B

Pad

Any container can be annotated with pad to apply a consistent padding drawn from the semantic spacing scale.

AttributeValuesDefaultNotes
padtight | group | form-row | block | section(none — absent = no padding)Any element

Live examples

pad="tight"

Child

pad="group"

Child

pad="form-row"

Child

pad="block"

Child

pad="section"

Child

Stat block

A labeled numeric tile — mono value on top, mono uppercase label beneath. Used for stat readouts (HP, AP, shield, XP, etc.). Use inline-flex so tiles line up cleanly in a row.

AttributeValuesDefaultNotes
sizesmall(none = default)Compact variant for inline usage

Live examples

Default

14/16 HP 6 Armour 1 Shield 30 XP

size="small"

4 Focus 3 Will 0 Fatigue

Page head

A page-header primitive with five slots: optional in-head breadcrumbs, a mono uppercase kicker, a native h1 (overrides global centred h1 to left-align inside page-head), and a page-meta row that holds sub + actions. On tablet+ (≥48em), sub and actions share a bottom-aligned row; on mobile they stack column.

ElementValuesDefaultNotes
page-crumbsIn-head breadcrumb slot. Contains a nav/ol trail. Used when SuppressBreadcrumbs is set on LayoutProps.
page-kickerMono uppercase, muted.
h1Native h1 inside page-head. page-head h1 scoped selector sets text-align: left and margin: 0, overriding the global centred h1.
page-metaWrapper for sub + actions. Column on mobile, bottom-aligned row at ≥48em.
page-subCaps at var(--max-prose) (68ch). Child of page-meta.
page-actionsWraps; pushes to right at ≥48em via margin-left: auto. Child of page-meta.

Live examples

Minimal

A Simple Page

Full

CHARACTER · LEVEL 4

Serina of the Fourth Coast

A wandering swordmage who left the Academy before her final rites. She keeps her reasons to herself and her blade close at hand.

With breadcrumbs

RULEBOOK

Introduction

What Soltherra is, how it works at a high level, and where to go once you have read this page.

Section heading (h2)

h2 is the default section-divider heading — mono bold with top border and section margin. Use variant="plain" to opt out (for dialog titles, stat labels, mid-card subheads).

AttributeValuesDefaultNotes
variantplain(none = section-divider default)Opt-out: removes border/margin/padding

Live examples

Subsequent h2 (divider visible)

A paragraph precedes this h2, so it is not first-child.

Second h2 — has divider

Top border and section margin are visible above this heading.

Plain variant (no divider regardless of position)

A paragraph precedes this h2, but the plain variant suppresses the divider.

Plain h2 — no divider

No top border or margin regardless of position in the container.


Buttons

Native <button> is the canonical action element. Attribute variants cover visual tones; the [block] attribute makes it full-width. For icon-only actions, use <icon-button> which enforces a 44×44 hit target.

ElementAttributeValuesDefaultNotes
buttonvariantprimary, ghost, danger, damage(none = default)default has dashed-border "quiet" look (pre-existing; restyle is a separate ticket)
buttonsizesmall(none = default)existing; no change
buttonblock(boolean)full-width
icon-button44×44 hit target; wrap a <button> or <a> inside for semantics

Live examples

Default variants

Sizes

Block (full-width)

Icon buttons


Callouts

Use <rule-callout> to pull out guidance, warnings, or constraints inside rule prose. The variant attribute picks tone; add a <rule-callout-title> child for a mono uppercase label.

ElementAttributeValuesDefaultNotes
rule-calloutvariantnote, warning, constraint, danger(none = default)left-border color changes per tone
rule-callout-titleoptional mono uppercase label at top

Live examples

Tip

Default callout — no variant. Use this for general guidance that doesn't carry a strong tone.

Note

Accent-tinted callout for things worth flagging but not alarming.

Warning

Warm-tinted callout for situations that can go wrong if you're not careful.

Constraint

Error-tinted callout for hard limits and rules that cannot be bent.

Danger

Strong error-tinted callout for consequences that are immediate and severe.


Meter bar

A generic progress bar for health, XP, fatigue, cooldowns. Height is fixed at 12px. Fill width comes from inline style="width: N%" on the inner <meter-bar-fill>. data-state on the outer element picks the fill color.

ElementAttributeValuesDefaultNotes
meter-bardata-statewounded, dying, dead(none = healthy/green)picks fill color
meter-bar-fillstyle="width: N%"requiredauthor sets percentage

Live examples

Healthy (75%)

Wounded (45%)

Dying (15%)

Dead (0%)


Info pill

Small mono tag/badge for statuses, labels, enum values. The variant attribute tunes tone; use sparingly so the accent pulls attention where it matters.

ElementAttributeValuesDefaultNotes
info-pillvariantaccent, warm, good, bad(none = neutral)tone

Live examples

Neutral Accent Warm Success Error

Site shell

The site shell is the persistent frame surrounding all page content: a sticky header across the top, a nav panel that stacks below the header on narrow viewports and moves to the left at ≥64em (ships in T-DSA-3), and a footer at the bottom.

Mobile (stacked)          Wide (≥64em) ┌─────────────────────┐   ┌────────────────────────────┐ │      site-header    │   │        site-header         │ ├─────────────────────┤   ├──────────┬─────────────────┤ │      nav-panel      │   │ nav-     │                 │ ├─────────────────────┤   │ panel    │    site-main    │ │      site-main      │   │          │                 │ ├─────────────────────┤   ├──────────┴─────────────────┤ │      site-footer    │   │        site-footer         │ └─────────────────────┘   └────────────────────────────┘

site-header

Sticky banner rendered once at the top of every page. Contains the site logo and (on narrow viewports) a nav-toggle.

AttributeValuesDefaultNotes
No attributes currently.

Inner-wrap at --max-page ships in T-DSA-3.


site-logo

Brand-mark plus wordmark composite. Composed of a site-logo-mark (26×26 square brand-mark with serif "S") and site-logo-text (wordmark), separated by var(--space-tight).

ElementRoleNotes
site-logoContainerInline-flex row with gap: var(--space-tight).
site-logo-markBrand-mark26×26 square, var(--font-display), inverted colours. Add aria-hidden="true".
site-logo-textWordmarkvar(--font-display) at 1.25rem, tracking −0.01em.
Soltherra

nav-panel / nav-group

Forward-declared — ships in T-DSA-4. Flat link list with top-border group separators (nav-group) and a 2px inset-left accent indicator on the current page link.


site-footer

Full-viewport-width footer with a two-column inner wrapper. Brand column on the left, link navigation on the right. Columns stack on narrow viewports. Inner wrapper centers at --max-page.

© Soltherra RPG System


Colors — flank zones

Four tokens used by the combat flanking widget only.

--color-flank-front
--color-flank-side
--color-flank-rear
--color-flank-behind