Design system

A ledger for code

Every page on reuseforge.com is built from the tokens and components below. This page is the reference: each component is shown in the states it can be in, in the theme you are using now. Switch theme and language from the header to check both.

rf.css Geist + Geist Mono WCAG 2.2 AA Internal page, not indexed

Principles

ReuseForge moves money against code, so the interface should feel like a ledger: ruled, exact and calm. Hairlines carry structure. Colour carries meaning. Numbers line up.

  1. A ledger for codeRows, rules and columns before boxes and shadows. A plan, a receipt and a log are all tables you can audit.
  2. One ember accentOrange marks the one thing to do or the one thing that matters on a screen: the primary action, a link, the current step. Status colours (green, red, amber, blue) only ever mean status.
  3. Hairlines, not shadows1px borders in --line. Shadows appear only on things that float: dialogs, toasts, hovered links.
  4. Mono for money, ids and hashesAmounts, order ids, hashes, versions and code use Geist Mono with tabular numerals, so columns of figures align and ids can be compared by eye.
  5. Say the statusPublic beta, test mode, unaudited: if it is true, the page says it, in words, next to the thing it applies to. Never rely on colour alone.

Do

  • Use tokens (var(--line)), never raw colours.
  • Show the real product: a plan, a receipt, a config snippet.
  • Put amounts in mono, right-aligned, with two decimals.
  • Keep one primary button per view.
  • Write short, concrete sentences.

Don't

  • Add inline styles, inline scripts, remote fonts or images (CSP).
  • Use ember for large fills, gradients or body text.
  • Use emoji as icons, or stock illustrations, or invented logos and numbers.
  • Convey state by colour only.
  • Rename or remove an existing class or token.

Colour

Cool iron neutrals with a single ember accent. Tokens are defined on :root in OKLCH and redefined for dark mode, which follows the operating system and can be forced with data-theme="light|dark" on html. Swatches below render with the active theme. Contrast ratios are measured by scripts/contrast_check.py (light / dark) and the script exits non-zero if a text pair drops under its threshold (4.5:1 for text, 7:1 for body text). Re-run it after any token change. The table below is generated from site/assets/rf.css by scripts/gen_design_contrast.py on every build, so the numbers are always current.

Tokens: name, role, light value, dark value, measured contrast (light / dark)

Colour tokens with light and dark values and contrast ratios
SwatchTokenLightDarkContrast, light / dark
Surfaces
--bg
Page background
oklch(.985 .003 255)
#f9fafc
oklch(.17 .012 258)
#0c1015
surface or hairline, no text
--side
Sidebar, footer, alternate sections
oklch(.958 .006 255)
#eef1f5
oklch(.205 .014 258)
#13171e
surface or hairline, no text
--panel
Cards, inputs, dialogs
oklch(1 0 0)
#ffffff
oklch(.225 .014 258)
#181c22
surface or hairline, no text
--panel2
Code, table hover, empty states
oklch(.972 .004 255)
#f4f6f8
oklch(.2 .013 258)
#12161c
surface or hairline, no text
Text
--fg
Body text and headings
oklch(.2 .018 258)
#11161e
oklch(.945 .006 258)
#eaedf1
on bg 17.34 / 16.28 AAA
on panel 18.10 / 14.56 AAA
on side 16.02 / 15.25 AAA
on panel2 16.70 / 15.41 AAA
--mute
Secondary text
oklch(.45 .02 258)
#4e5661
oklch(.72 .015 258)
#9fa5ae
on bg 7.12 / 7.71 AAA
on panel 7.43 / 6.90 AA
on side 6.58 / 7.22 AA
on panel2 6.86 / 7.30 AA
--faint
Hints, placeholders, captions
oklch(.52 .018 258)
#636974
oklch(.66 .016 258)
#8c939c
on bg 5.27 / 6.15 AA
on panel 5.50 / 5.50 AA
on side 4.87 / 5.76 AA
on panel2 5.08 / 5.82 AA
Lines
--line
Borders, dividers
oklch(.875 .008 255)
#d2d6db
oklch(.34 .016 258)
#333840
surface or hairline, no text
--line2
Row separators inside tables
oklch(.93 .005 255)
#e6e8eb
oklch(.28 .014 258)
#252930
surface or hairline, no text
--line-strong
Hover borders, empty-state dashes
oklch(.75 .012 255)
#a9aeb6
oklch(.46 .02 258)
#515963
on bg 2.13 / 2.68 hairline
--ctl-border
Form control borders (WCAG 1.4.11, 3:1)
oklch(.62 .014 256)
#81878f
oklch(.54 .014 258)
#6a6f77
on bg 3.49 / 3.78 FAIL
on panel 3.64 / 3.38 FAIL
on side 3.22 / 3.54 FAIL
on panel2 3.36 / 3.58 FAIL
Ember accent
--acc
Text-safe ember: links, eyebrows, highlights, focus ring
oklch(.53 .14 50)
#a94e00
oklch(.76 .16 52)
#ff9044
on bg 5.34 / 8.49 AA
on panel 5.57 / 7.59 AA
on side 4.93 / 7.95 AA
on panel2 5.14 / 8.03 AA
on acc-soft 4.84 / 6.17 AA
--acc-fg
Text on a solid --acc fill
oklch(.995 .004 60)
#fffdfb
oklch(.2 .02 55)
#1d140d
on acc 5.49 / 8.06 AA
--acc-soft
Accent tint behind badges and selection
oklch(.955 .03 55)
#ffebde
oklch(.3 .06 45)
#462312
surface or hairline, no text
--acc-line
Accent hairline
oklch(.82 .09 48)
#f5b391
oklch(.5 .12 48)
#984a1a
surface or hairline, no text
--ember
Brand fill: logo, primary button, marks. Never text
oklch(.72 .175 52)
#f77f20
oklch(.76 .16 52)
#ff9044
surface or hairline, no text
--ember-fg
Ink on the ember fill (primary button label)
oklch(.2 .02 55)
#1d140d
oklch(.2 .02 55)
#1d140d
on ember 6.92 / 8.06 AA
on ember-hover 6.16 / 9.03 AA
on ember-active 5.27 / 6.96 AA
--ember-hover
Primary button, hover
oklch(.69 .175 50)
#ee7418
oklch(.79 .14 54)
#ffa05e
surface or hairline, no text
--ember-active
Primary button, pressed
oklch(.65 .17 48)
#df6815
oklch(.72 .155 50)
#f08440
surface or hairline, no text
--ember-line
Primary button border
oklch(.62 .15 50)
#cb6620
oklch(.6 .13 50)
#bc662f
surface or hairline, no text
Status
--good
Success, released, passing
oklch(.46 .12 150)
#0f6a31
oklch(.78 .14 150)
#6fd087
on bg 6.46 / 10.08 AA
on panel 6.74 / 9.01 AA
on good-soft 5.98 / 7.55 AA
--good-soft
Success tint
oklch(.955 .035 150)
#e0f7e4
oklch(.28 .05 150)
#15301b
surface or hairline, no text
--bad
Error, refund, destructive
oklch(.5 .2 23)
#ba0426
oklch(.74 .15 27)
#fb8276
on bg 6.40 / 7.79 AA
on panel 6.68 / 6.96 AA
on bad-soft 5.85 / 6.08 AA
--bad-soft
Error tint
oklch(.96 .025 23)
#ffecea
oklch(.28 .06 27)
#421c18
surface or hairline, no text
--warn
Caution, test mode, money safety
oklch(.5 .11 70)
#8a5600
oklch(.82 .12 80)
#edbb64
on bg 5.89 / 10.84 AA
on panel 6.15 / 9.69 AA
on warn-soft 5.46 / 8.04 AA
--warn-soft
Caution tint
oklch(.96 .045 85)
#fff0d0
oklch(.29 .05 80)
#38280a
surface or hairline, no text
--info
Neutral information
oklch(.5 .13 255)
#2863ab
oklch(.78 .11 255)
#87bafd
on bg 5.78 / 9.57 AA
on panel 6.04 / 8.56 AA
on info-soft 5.30 / 7.30 AA
--info-soft
Information tint
oklch(.955 .025 255)
#e5f1ff
oklch(.28 .045 255)
#192a3f
surface or hairline, no text

Rules

  • Text uses --fg, --mute or --faint on --bg, --panel, --side or --panel2. All pass 4.5:1.
  • The primary action is a solid --ember fill with --ember-fg ink, the same orange as the logo and the share card, in both themes. Links, eyebrows and highlights use --acc, the same hue family darkened until it is text-safe. --ember is never used for text.
  • Status pairs a -soft background with the matching strong colour for text, and always includes a word or a shape.
  • Mixing is done with color-mix(in oklch, var(--x) 22%, transparent), so tints follow the theme.

Type

Geist for text and Geist Mono for money, ids, hashes and code, both self-hosted (Latin subset). Chinese falls back to the system CJK font (PingFang SC, Hiragino Sans GB, Microsoft YaHei, Noto Sans SC) with relaxed line height and no letter spacing. Body text is 15px on a 1.55 line; prose is 16px on 1.7.

Scale: --fs-xs 12, --fs-sm 13, --fs-base 15, --fs-md 16, --fs-lg 18, --fs-xl 20-26, --fs-2xl 28-38, --fs-3xl 34-64

.h-display

Reuse first. Build last.

h1

Plan the cheapest safe build

h2

Escrow holds the money until you confirm

h3

Acceptance window

h4

Delivery deadline

.lead

Give a goal and a budget. The planner returns the best mix of blocks to reuse, buy or build.

body, 15/1.55

Sellers are paid only when the buyer confirms delivery. Until then the funds sit in escrow and can be refunded.

.small, .mute

Fees are shown before you confirm. Test mode uses a simulated rail.

.eyebrow

Hash-chained log

.mono, .tnum

USD 1,250.00   ord_7f3a9c   sha256:9b2e41c0d8

code, kbd

Run repoblock plan then press Enter.

:lang(zh)

用目标和预算换来一份可执行的复用方案。托管中的款项,只有在买家确认后才会放款。

支持 MCP、USDC 与 Pareto 前沿,测试模式使用模拟支付通道。

Space and shape

A 4px base. Components use 8, 12, 16 and 24; sections use clamp() so the rhythm tightens on phones. Corners are small (6 to 8px); pills are for status only.

--sp-1 to --sp-8

--sp-14px
--sp-28px
--sp-312px
--sp-416px
--sp-524px
--sp-632px
--sp-748px
--sp-864px

Radius: --r-sm 6, --r 8, --r-lg 14, --r-pill. Elevation: border, --sh1, --sh2

r-sm
r
r-lg
r-pill

Elevation

hairline
--sh1
--sh2

Layout

.wrap centres content at 1120px with a fluid gutter (16 to 32px) and respects safe areas. .wrap.narrow is 720px. .section gives vertical rhythm; .section.alt shades it with --side. Use .grid for equal columns, .split for a 5/7 text-and-visual pair, .stack for vertical rhythm and .row for inline groups.

.grid.cols-3 collapses to 2 columns at 860px and 1 at 560px; .grid.auto fits as many 240px columns as there is room for

1
2
3
auto
auto
auto
auto

.split, .stack.stack-sm, .row.between

.stack-sm item
.stack-sm item
7fr column
leftright

Buttons

One primary button per view. Use a.btn for navigation and button.btn for actions. Targets are at least 30px tall; the default is 38px. Standalone links and links in table cells are at least 24px tall (.hit).

.btn .pri / .sec / .ghost / .danger

Link button

Sizes: .sm 30px, default 38px, .lg 48px

States: disabled, aria-busy (loading with .spinner), hover and focus are live, press the button below

Icon only needs an accessible name: .btn.icon, .btn.icon.sm, .btn.icon.lg. With icon: svg 16px inside .btn. Group: .btn-group

.btn.block fills the container (use on phones)

Forms

Every control has a visible label. Help sits under the field and is linked with aria-describedby. Errors set aria-invalid="true", use .err text that names the fix, and are announced by a live region. Inputs are 16px on phones so iOS does not zoom.

.field > label + input, .help, .err, [aria-invalid], :disabled, [readonly]

Plain English. The planner reads English only.

Enter at least 1.00. Budgets are in US dollars.

Choice: .check with checkbox, radio, switch (input role=switch), range. Group radios in a fieldset with a legend

Risk tolerance
Kinds
On

.input-group with a button; .input-group.joined with an .addon; .input.sm

USD

Badges and tags

Badges state a status and always carry a word. Kind chips (.kind) name what a block is, in mono. Tags (.tag) are neutral topics; as links they highlight on hover.

.badge, .good .warn .bad .info .acc, .mono; .dot

Draft Released Test mode Refunded In escrow Recommended sha256:9b2e41 Live

.kind: repo, skill, harness, mcp_server, model, dataset, design, paper

reposkillharnessmcp_servermodeldatasetdesignpaper

.tag, a.tag, .chip with aria-pressed (filter), .avatar, .avatars

retrieval-ragagentslinked tag
abc

Notes and callouts

Notes interrupt: use them for status, safety and money. Use warn for money and test mode, bad for execution risk, info for tips and good for confirmation. Put the point in the first sentence, in bold if it is a command. A callout is quieter: a key fact inside running content.

.note .info / .good / .warn / .bad, with and without an icon

Tip. The planner reads English. Describe the goal in one or two sentences.
Released. The buyer confirmed delivery and the seller was paid.
Test mode. Payments use a simulated rail. Do not send real funds.
Review the code first. A block runs with your agent's permissions.
A plain note with no status, for neutral context.

.callout

Sellers are paid on release

The escrow agent releases funds only when the buyer confirms, or after the acceptance window if the buyer does not dispute.

Tables and figures

Wrap tables in .scroll so they scroll inside their box on phones, and give the wrapper tabindex="0", role="region" and a label so keyboard users can scroll it. Numbers use .num: right-aligned, mono, tabular. Headers for numeric columns also get .num.

table, .num, td.id, tfoot, .scroll.box. Sample data, not a real order

Sample plan
RequirementChoiceQualityRiskCost (USD)
RetrievalReuse repo0.820.120.00
Tool serverBuy mcp_server0.740.1840.00
EvaluationBuild from paper0.660.35120.00
Total0.740.22160.00

.stat-row > .stat; .kv (receipt rows); .hash. Sample data

3Blocks reused
$160.00Planned spend
0.74Plan quality
0.22Plan risk

Receipt

Order
ord_7f3a9c21
Amount held
$160.00
Fee (1%)
$1.60
Rail
mock (test mode)
Status
In escrow

prev 4e1c0a87d2b3f9a6
this 9b2e41c0d8a57e33

Steps and timeline

Progress through an order is shown with text first. The marker shape differs too: filled means done, ringed means current, square means refunded. .steps is horizontal and fits four stages; .timeline is vertical and carries timestamps.

.steps with li.done and li.cur; .steps.refund

  1. Funded
  2. Delivered
  3. Confirm
  4. Released
  1. Funded
  2. Delivered
  3. Refunded
  4. Closed

.timeline with li.done, li.cur, li.refund. Sample data

  1. Funded in escrow
  2. Seller delivered
  3. Waiting for buyer to confirm
  4. Released to seller
  1. Funded in escrow
  2. Dispute raised
  3. Refunded to buyer

Empty, loading, progress

Every list has an empty state that says what is missing and what to do next. Skeletons hold the layout while data loads and stop animating when reduced motion is on. Progress bars take their width from --v; set it with data-v="40" (rf.js applies it, CSP-safe) or el.style.setProperty('--v', 40).

.empty

No orders yet

Run a plan, then place an order for the paid items. Orders and their escrow state show up here.

Plan a build

.sk (alias .skeleton): .title, .block, .circle, .btn; width helpers .w-25 .w-60 .w-75

Loading

.progress > i[data-v], .progress.indet, native progress, .meter.good/.warn/.bad, .meter-row, .spinner

Funding 62% of the target

Animated with --v from script

Indeterminate

40%
Quality0.82
Cost0.55
Risk0.71
Planning

Dialog and toast

Dialogs are native dialog.modal: focus is trapped and restored by the browser, Esc closes, a click on the backdrop closes. Open with data-open-dialog="id", close with data-close-dialog. Toasts call RF.toast(message, kind) and are also announced to screen readers.

dialog.modal + [data-open-dialog]; RF.toast('...', 'good' | 'bad')

Confirm release

Releasing pays the seller $40.00 and closes the order. This cannot be undone.

Sample dialog. Nothing is charged.

Cards

A card is a bordered panel. Padding is opt-in (.pad) so a card can hold a table or a code block edge to edge. A card that links is an a.card, with one heading and a short line.

.card.pad, a.card, .card.flat, .card.acc, .card with .hd .bd .ft

Plain card

Panel background, hairline border, 8px radius.

Linked card

The whole card is the link. Hover raises the border and adds a shadow.

Flat card

Uses --panel2 for quiet groupings inside a panel.

Accent card

One ember rule on top. Use once per view.

With sections

Header, body and footer share the card border.

Updated 2026-11-03

Code

Blocks use .code with an optional .bar (file name or language) and a copy button with data-copy pointing at the pre. Give the pre tabindex="0" so keyboard users can scroll it. Syntax classes: .tk-k keyword, .tk-s string, .tk-c comment, .tk-n number.

.code > .bar + pre; button.btn.ghost.sm.copy[data-copy]

plan.json
// Ask for a plan. Budget is in USD.
{
  "goal": "Add retrieval to a support agent",
  "budget_usd": 200,
  "max_risk": 0.3
}

Inline: code, kbd, .hash

Set REPOBLOCK_RAIL=mock and press Ctrl + K. Entry hash 9b2e41c0d8a57e33f1c0a4b2e8d97a10

Prose

.prose styles long-form content (docs, legal): 16px on a 1.7 line, 720px measure, anchored headings, tables in .scroll. The docs layout adds a section nav and an on-page table of contents.

.prose

How escrow works

Money for paid items is held until the buyer confirms. Here is the sequence, and what each party can do.

The buyer funds an order. The seller delivers. The buyer then confirms or raises a dispute within the acceptance window. Read the full order record in the API.

  • Default acceptance window: 7 days.
  • Default delivery deadline: 7 days.
  • Network fee on a self-hosted node: 1%.
Test mode uses a simulated rail. No real funds move.
Unaudited. The escrow contract is not deployed and has not been audited.
StateWho actsDays
fundedSeller delivers7
deliveredBuyer confirms7

Accessibility

The target is WCAG 2.2 AA. These are built in; pages must not break them.

  • Focus. A 2px --acc ring with 2px offset on every interactive element (:focus-visible). Inputs add a halo and keep a transparent outline for forced-colors mode. Scroll regions and tabs use an inset ring so overflow does not clip it.
  • Targets. Buttons are 30px or taller (default 38px, 44px in the mobile menu); standalone and table-cell links are at least 24px (WCAG 2.5.8). Checkbox and radio rows are whole-label targets.
  • Colour. Text pairs are at least 4.5:1 in both themes. Status always has a word or a shape beside the colour.
  • Structure. One h1 per page, ordered headings, main with a skip link, labelled navigation landmarks.
  • Motion. prefers-reduced-motion turns off all transitions and animations. Smooth scrolling is opt-in only when motion is allowed.
  • Modes. prefers-contrast: more raises borders and secondary text; forced-colors keeps borders, focus and progress visible; print drops chrome and shows link addresses.
  • Language. Set lang on foreign-language fragments. Chinese pages use :lang(zh) rules for line height and punctuation.

Live regions: RF.announce(msg) and RF.toast(msg) write to a polite role=status region

Utilities

Small helpers for the things components do not cover. Prefer a component class; reach for these to finish a layout.

.truncate, .clamp-2, .break, .hide-mobile, .show-mobile, .sr-only (.visually-hidden), .mute .faint .ok .bad .warn, .mono .tnum .nowrap .right .center, .hint, .more

.truncate cuts a very long single line with an ellipsis instead of wrapping or overflowing the card it sits in.

.clamp-2 limits text to two lines and hides the rest. Use it for card descriptions where listings vary in length, so a grid of cards keeps an even rhythm no matter how long the seller wrote.

https://reuseforge.com/app/orders/ord_7f3a9c21d0b84e6fa1c3e5d7b9f2a4c6e8d0b1a3c5e7f9a2b4d6e8f0a1c3e5

Visible above 720px only (.hide-mobile).

Visible at 720px and below only (.show-mobile).

Passed · Failed · Caution · Faint · hint text · Read more

Header, footer, skip link and language switch come from site-src/layout.html and are not part of a page's content. The full class catalogue is in docs/DESIGN_SYSTEM.md.