FORK

Components

Interactive UI elements — inputs, display, chat, feedback, and content rendering

Button

Primary
Primary Tinted
Secondary
Tertiary
Destructive
Icon
Default
Hover Web
Loading
Pressed
Focus
Disabled
Dimensions
Large height 50pt
Medium height 40pt
Small height 32pt
Shape Capsule (radiusFull)
Corner curve .continuous
Content Insets
Large 12pt × 24pt
Medium 8pt × 16pt
Small 4pt × 12pt
Tertiary large 8pt × 16pt
Icon padding uniform (square)
Typography
Large title iOS 17pt Web 17px semi
Medium title iOS 14pt Web 14px semi (typeFootnote)
Small title iOS 13pt Web 13px semi (typeCaption1)
Large subtitle iOS 13pt Web 14px regular (typeFootnote)
Font family Figtree (semibold title / regular subtitle)
Colors
Primary bg label / systemBackground (inverted)
Primary fg systemBackground / label (inverted)
Primary Tinted bg tint (glass on iOS 26)
Primary Tinted fg onLight (white)
Secondary bg systemFill (glass on 26)
Tertiary bg transparent
Disabled content alpha 38%
Highlight Animation
Scale (default) 0.96× · spring(0.9, 0.1)
Lift (iOS 26) 1.04× + shadow · spring(0.85, 0.2)
Release spring(0.4, 0.3) / (0.5, 0.35)
Haptic .soft impact on touchUpInside
Web Interaction Web
cursor pointer (default) · not-allowed (disabled)
:hover box-shadow glow (filled) · bg tint (plain)
:active scale(0.96)
transition transform 0.18s ease-out, box-shadow 0.2s
Loading State
Indicator sizes 16 · 24 · 32pt
Stroke width 2 · 2.8 · 3.6pt
Rotation 0.42 · 0.46 · 0.50s
Gracetime (light) 1.2s delay
Gracetime (regular) 0.28s delay
Glass Buttons iOS
iOS 26+ / UIGlassEffect
  • Primary.filled() — opaque label/systemBackground inverted
  • Primary Tinted.prominentGlass() — full refraction, tinted
  • Secondary.glass() — translucent with label foreground
  • Icon.glass() + .systemGray5 tint
  • isInteractivetrue — adds scale/bounce/shimmer on press
Filled Buttons iOS
iOS 16–25 / UIButton.Configuration
  • Primary.filled() — opaque label/systemBackground inverted
  • Primary Tinted.filled() — opaque tint background
  • Secondary.filled() + .secondarySystemFill
  • Icon.filled() + .secondarySystemFill
  • HighlightScale animation (0.96×) with spring timing
CSS Buttons Web
CSS custom properties
  • Primary.btn--primary — opaque label bg, inverts in dark
  • Primary Tinted.btn--primaryTinted — tint bg, onLight text
  • Secondary.btn--secondary — systemFill bg, label text
  • Icon.btn--icon — systemFill bg, fixed square
  • Interaction:hover glow, :active scale(0.96)
When to use
  • Primary/Main CTA — sign up, confirm, continue. One per screen.
  • Primary Tinted/Brand-colored CTA — checkout, key actions where tint reinforces brand.
  • Secondary/Supporting actions — settings, filters, edit. Lower visual weight than primary.
  • Tertiary/Minimal actions — "learn more", "skip", cancel. Text-only, no fill.
  • Destructive/Delete, remove, sign out. Red fill signals danger. Pair with confirmation dialog.
  • Icon/Toolbar actions, compact controls. Square shape, single icon, no label.
Platform notes
  • iOS Button.Traits/State struct controlling content (title, subtitle, image) and style. Assign via button.traits = .primary(title: ...)
  • iOS Factory methods/.primary() .primaryTinted() .secondary() .tertiary() .destructive() .icon()
  • iOS ActionKit/Async actions with automatic loading state. Gracetime prevents spinner flash on fast operations.
  • iOS ContentView/StatefulView subclass — constraint swapping animates layout changes (title, image, loading)
  • iOS .shakeOnError/Option flag — horizontal shake animation when async action completes with error
  • Web CSS classes/.btn .btn--primary .btn--lg — compose style + size. All CSS custom properties from this design language.
  • Web Markup/<button class="btn btn--primary btn--lg"><span class="btn-label">...</span><span class="btn-spinner"></span></button>
  • Web Loading/Add .btn--loading class. Spinner animates via CSS keyframes. Implement gracetime delay in JS.
  • Web Disabled/HTML disabled attribute. Opacity 38%, no pointer events, no transform.
  • Web Accessibility/Use <button> not <div>. Add aria-busy="true" during loading, aria-disabled for disabled state.
  • Android Compose Button/Button(onClick, colors = ButtonDefaults.buttonColors(containerColor = tint, contentColor = onLight)). Map each style variant to custom buttonColors().
  • Android Ripple/Native ripple via indication = rememberRipple(). No overlay simulation needed — Android provides this by default.
  • Android Tap target/Minimum touch target 48dp (Material guideline, 4dp larger than iOS 44pt). Use Modifier.minimumInteractiveComponentSize().

Input

FILLED
PLACEHOLDER — click to see focus ring
SEARCH (CAPSULE)
OUTLINE
ERROR
Please enter a valid email address
DISABLED
CODE / MONO
Dimensions
Default height 44pt (large tap target)
Search height 40pt
Filled radius radius10 (10pt)
Search radius radiusFull (capsule)
Colors
Background systemFill
Text label
Placeholder tertiaryLabel
Error border destructive
Disabled content alpha 38%
When to use
  • Filled/Default input style — forms, settings, profile editing. systemFill background on any surface.
  • Outline/URL inputs, code fields, contexts where fill blends too much with background.
  • Search (capsule)/Global search bars, filter inputs — pairs with capsule buttons visually.
  • Mono/API keys, URLs, code snippets, technical identifiers — IBM Plex Mono for clarity.
  • Body font/All text inputs use Figtree. iOS 17pt Web 18px. Placeholder at tertiaryLabel.
Platform notes
  • Web Focus ring: border-color: var(--tint); box-shadow: 0 0 0 3px var(--selection) on focus. Never remove focus indicator for keyboard users.
  • Web Always set font-family: inherit and font-size: inherit on inputs. Browsers default to system font otherwise.
  • Web Use font-size: 16px minimum on mobile web to prevent iOS Safari auto-zoom on focus.
  • Web autocomplete, inputmode, enterkeyhint attributes improve mobile keyboard experience.
  • iOS Uses UITextField with custom appearance. Focus managed via becomeFirstResponder/resignFirstResponder.
  • Android Use TextField or OutlinedTextField in Compose. Map focus ring to focusedBorderColor, fill to containerColor via TextFieldDefaults.colors().
  • Android Software keyboard: use WindowInsetsCompat.ime() to handle keyboard inset. Pair with BringIntoViewRequester so focused inputs scroll into view automatically.

Switch

STATES
Off
On
Disabled
IN LIST CONTEXT
Notifications
Receive push notifications
Dark Mode
Use dark color scheme
Dimensions
Track width48pt
Track height28pt
Thumb size20pt
Thumb inset4pt from edge
Tap target48 × 44pt minimum
Colors
Off tracksystemFill + separator border
Off thumblabel
On tracktint
On thumbonLight
Disabled38% opacity
Animation
Thumb slide0.25s easeSpring
Track fill0.25s easeFluent
Haptic.light impact on toggle
When to use
  • .switch/Binary on/off settings that take effect immediately — no submit button needed.
  • vs. checkbox/Checkboxes are for selection in forms (submitted together). Switches are for instant toggles.
  • label pairing/Always pair with a text label. The switch alone doesn't communicate what it controls.
Platform notes
  • iOS Toggle(isOn: $value) with .tint(Color.tint). Native UISwitch handles animation and haptics.
  • Web Uses <label class="switch"><input type="checkbox"><span class="switch-track"></span></label>. Hidden checkbox, styled track with ::after pseudo-element thumb.
  • Web :focus-visible ring on the track for keyboard accessibility. Tab navigable via the hidden input.
  • Android Use Switch composable. Map tint to checkedThumbColor + checkedTrackColor via SwitchDefaults.colors().
  • All Spring easing on thumb movement (easeSpring) matches the physical feel of a real toggle.

Avatar

SIZES
S
MD
LG
XL
VARIANTS — INITIALS / AI / IMAGE
JS
User photo
STATUS — ONLINE / OFFLINE / BUSY
JS
AB
CD
GROUP — STACKED
A
B
C
+2
Dimensions
Small28pt
Medium36pt
Large48pt
Extra large64pt
ShapeCircle (50%)
Colors
Default bgtertiarySystemFill
InitialssecondaryLabel
AI bgtint
AI fgonLight
Status ringsystemBackground (2px)
Status Indicator
Size10pt (incl. ring)
Onlinesuccess
OfflinesystemGray
Busydestructive
PositionBottom-right, edge-aligned
Group
Overlap-8pt margin-left
Ring2px systemBackground
Max visible4 + overflow count
When to use
  • .avatar--sm/Inline mentions, compact lists, message timestamps. 28pt — fits beside caption text.
  • .avatar--md/List items, chat headers, comment threads. 36pt — default for most UI contexts.
  • .avatar--lg/Chat messages, user cards, profile previews. 48pt — primary identification.
  • .avatar--xl/Profile headers, user detail views. 64pt — hero-level display, one per screen.
  • .avatar--ai/AI agent identity. Tint background distinguishes agents from users. Use with an icon, not initials.
  • .avatar-group/Shared spaces, group chats, collaborators. Show up to 4 with overflow count.
Platform notes
  • iOS Use AsyncImage with .clipShape(.circle). Placeholder shows initials while image loads.
  • iOS Status indicator via overlay with .alignmentGuide(.bottom, .trailing). Ring matches parent background.
  • Web <div class="avatar avatar--lg">JS</div> for initials. Nest <img> for photos. object-fit: cover handles non-square images.
  • Web Group overlap uses negative margin + box-shadow ring (not border, to avoid doubling).
  • Android Use AsyncImage (Coil) with Modifier.clip(CircleShape). Placeholder composable shows --systemFill mapped color while loading.
  • All AI avatars always use --tint, which updates when the user swaps tint presets.

Badge

COUNT BADGES
3 12 99+
DOT BADGES
ON AVATAR
JS
5
Dimensions
Count height18pt
Count min-width18pt
Count padding0 × 5pt
Dot size8pt
ShapeCapsule (radiusFull)
Colors
Defaultbadge (red)
Tinttint (brand color)
Successsuccess
Warningwarning (onDark text)
TextonLight (Mono 11px 700)
When to use
  • .badge/Unread count on tabs, nav items, avatar. Red (badge token) = requires attention.
  • .badge--tint/Brand-colored notification — new feature, promotion, non-critical update. Uses tint color.
  • .badge--dot/Binary status indicator — new content, online presence. No count, just presence.
  • .badge--success/Positive status — connected, verified, complete.
Platform notes
  • iOS Use .badge(_ count:) modifier on TabView items. Custom badge via overlay anchor.
  • Web Position with position: absolute; top: -4px; right: -4px; on parent with position: relative.
  • Android Use Badge composable from Material 3 with BadgedBox. Override colors with FORK tokens via BadgeDefaults.containerColor.
  • All Cap count at 99+ to prevent layout overflow. Animate count changes with spring timing.

Tag

PLATFORM
iOS Android Web All
SEMANTIC
Featured Breaking Default Draft
IN CONTEXT
Use UINavigationBar large title on iOS and TopAppBar on Android. On Web use clamp() for fluid sizing. The New tag marks recently added tokens.
Dimensions
FontIBM Plex Mono · 11px · 700
Letter spacing0.06em
Transformuppercase
Padding2pt × 8pt
Radiusradius6
Line height1.2
Color Variants
iOSsystemBlue 15% fill + systemBlue text
AndroidsystemGreen 15% fill + systemGreen text
WebsystemOrange 15% fill + systemOrange text
AllsystemFill + secondaryLabel
Tinttint + onLight
Destructivedestructive + onLight
FillsystemFill + secondaryLabel
Outlinetransparent + separator border
When to use
  • .tag/Inline categorical label — platform, status, version, feature flag. Not interactive by default.
  • Platform variants/Flag platform-specific behavior in docs, usage lists, and spec tables. Color encodes the platform at a glance.
  • .tag--tint/Brand moments — new features, highlighted items, active states.
  • .tag--fill / .tag--outline/Neutral labels — categories, draft status, metadata. Low visual weight.
  • vs. Badge/Tags are inline categorical labels. Badges are notification dots / counters positioned on top of another element.
Platform notes
  • iOS Use UILabel with backgroundColor + textAlignment: .center + content insets. Or Compose-style: Text with padded background Shape.
  • Android Use Compose Surface(shape = RoundedCornerShape(6.dp), color = systemBlue) { Text(...) }. Map color variants to FORK token values — not Material 3 chip colors.
  • Web <span class="tag tag--ios">iOS</span>. Tags are inline elements — they flow with text. No tap target needed unless interactive.
  • All Tags are not interactive by default. If a tag is tappable (e.g., filter chip), wrap in a <button> and ensure 44×44pt tap target.

Tabs

SEGMENTED CONTROL
TAB BAR
Segmented Control
Track bgsystemFill
Track radiusradiusFull (capsule)
Track padding3pt
Item height32pt
Active bgsecondarySystemGroupedBg + shadowSmall
Active textlabel, semi (600)
Inactive textsecondaryLabel, medium (500)
Tab Bar
Item height44pt
Indicator2px bottom border, tint
Active texttint, semi (600)
Inactive textsecondaryLabel, medium (500)
Divider1px separator (bottom)
When to use
  • .segmented/Filter or view mode switching within the same content area. 2–4 options max. Compact, inline.
  • .tab-bar/Page-level navigation between distinct content sections. Scrollable on mobile. Visible section count can exceed 4.
  • vs. dropdown/Use tabs when options are few and should be always visible. Use dropdown when options exceed ~6 or are secondary.
Platform notes
  • iOS Segmented: Picker(.segmented). Tab bar: TabView with custom styling or .tabViewStyle(.page).
  • Web Segmented: <div class="segmented"><button class="segmented-item active">...</button>...</div>. Toggle .active via JS.
  • Web Tab bar scrolls horizontally on narrow viewports via overflow-x: auto. Hide scrollbar for clean appearance.
  • Web Use role="tablist" and role="tab" with aria-selected for accessibility.
  • Android Tab bar: TabRow composable with custom indicator colors. Segmented control: build a custom composable — Android has no native equivalent.

List

BASIC
AK
Anna Kim
Building a weather app...
3
FORK Agent
Ready to help
MR
Max Rivera
Shared "Recipe Tracker" with you
2m ago
Delete conversation
SECTIONS & UNREAD
Today
AK
Anna Kim
Hey, check out the new prototype I just pushed — let me know what you think about the transitions
2m 2
DT
Design Team
Julia: New icons uploaded to the shared library, please review when you get a chance
15m
MR
Max Rivera
Thanks for the review
1h
Earlier
JK
Julia Kim
Meeting rescheduled to Thursday
Yesterday
INLINE ACTIONS
SC
Sarah Chen
@sarah
AL
Alex Lee
@alexl
MR
Max Rivera
@maxr
HORIZONTAL
AK
Anna
MR
Max
JK
Julia
SC
Sarah
AL
Alex
TW
Tom
LM
Lisa
DP
Dan
SWIPE ACTIONS
JK
Julia Kim
See you tomorrow!
3h
AK
Anna Kim
Sounds good, let me know
5h
MR
Max Rivera
Thanks for the review
1d
Dimensions
Min row height44pt (tap target)
Paddingspace12 × space16
Gap (leading)space12
ContainersecondarySystemGroupedBackground · radius14 · 1px border
Separator1px separator (between items)
Typography
Titlesubheadline (16px) · 400 · label
Title (unread)subheadline (16px) · 600 · label
Subtitlecaption1 (13px) · secondaryLabel · 2-line clamp
Valuesubheadline · secondaryLabel
Section headeroverline (11px) · mono · 600 · uppercase · secondaryLabel
Destructivedestructive color on title
Unread State
Title weight600 (semibold)
Timestamptint color (optional, for emphasis)
Badgecounter in trailing (optional)
Horizontal
Gapspace16
Scrollhorizontal, hidden scrollbar
Labelcaption2 (12px) · label · max 64px · ellipsis
Activescale(0.95) · easeSpring
Swipe Actions
Action width80px per action
Icon20 × 20 · onLight
Labelcaption2 (12px) · 500 · onLight
Deletedestructive background
Archivetint background
MoresecondaryLabel background
Full swipetriggers trailing-most action (usually delete)
When to use
  • .list/Grouped vertical rows — settings, contacts, conversations, notifications. Rounded container with separator dividers.
  • .list-header/Section label between .list groups. Overline style (mono, uppercase). Use for temporal ("Today", "Earlier") or alphabetical grouping.
  • .list-item--unread/Unread/new state — bold title + tint timestamp. Badge in trailing for count. Combine with .list-item--top for chat/notification rows.
  • .list-item--top/Top-aligned layout — trailing slot becomes column (timestamp top, badge below). Use for rows with multi-line subtitle where meta should align with title.
  • .list-item--destructive/Danger actions — delete, leave, remove. Red title, typically no leading element.
  • .list-row/Horizontal scroll container — stories, suggested friends, categories. Items are .list-row-item with avatar/icon + label below.
  • .list-swipe-reveal/Swipe action reveal — trailing actions behind the row. Trailing-most action is the full-swipe target. Max 3 actions.
  • Inline actions: buttons in .list-item-trailing — use existing .btn--sm. For Follow/Unfollow, toggle between .btn--primaryTinted and .btn--secondary.
  • Subtitle truncation: all subtitles auto-clamp to 2 lines with ellipsis. No modifier needed — it's the default behavior.
Platform notes
  • iOS List { ForEach(...) } with .listStyle(.insetGrouped). Section headers: Section(header:). Swipe via .swipeActions(edge:) — leading for archive, trailing for delete. Use .listRowSeparatorTint(.separator).
  • iOS Unread: .fontWeight(.semibold) on title + tint color on timestamp. No dot — badge count and bold title are sufficient signals.
  • Android LazyColumn with ListItem composables. Section headers: sticky headers via stickyHeader { }. Swipe: SwipeToDismiss with custom background. Separators: Divider.
  • Web <div class="list"> wrapper + .list-item rows. Hover/active via CSS. Swipe not native on Web — use touch events or show actions on hover/long-press as fallback. Horizontal scroll via overflow-x: auto.
  • All 44pt minimum row height is non-negotiable. Subtitle clamps at 2 lines. Swipe actions: max 3 per side, 80pt each, trailing-most is the full-swipe target. Horizontal list must show peek of next item to signal scrollability.

Card

Weather App
Real-time forecasts and alerts
Task Tracker
Kanban board with AI sorting
Dimensions
Container radiusradius14
Header padding16pt uniform
Body padding16pt × 24pt
Footer padding12pt × 24pt (bottom 16pt)
Icon size44pt (radius10)
Interactive Behavior
HovertranslateY(-2px) + shadowMedium
Pressscale(0.98) · 0.12s easeStandard
Release0.25s easeSpring
Cursorpointer
When to use
  • .card/Static content container — wraps demos, forms, grouped content. secondarySystemGroupedBackground with border.
  • .card--interactive/Tappable card — app previews, dashboard widgets, content links. Adds hover lift and press scale.
  • .card-header/Icon + title + description row. Separated from body by separator border.
  • .card-footer/Metadata row — timestamps, counts, secondary info. Mono overline style.
  • .card-icon/44pt rounded square. Default tint background, override with inline style for custom colors.
Platform notes
  • iOS Interactive card: Button { CardContent() }.buttonStyle(.scale) with custom press animation (0.98× scale, spring timing).
  • iOS Card icon: RoundedRectangle(cornerRadius: 10, style: .continuous) with icon centered.
  • Web <div class="card card--interactive"> for clickable cards. Wrap in <a> for navigation.
  • Web Grid layout: .card-grid gives 2-column layout, collapses to 1 column at 720px.
  • Android Card composable with CardDefaults.cardColors() and CardDefaults.cardElevation(). Map shadow tokens to elevation dp values.

Empty State

No messages yet
Start a conversation with FORK Agent to build your first app.
Layout
Padding48pt × 24pt
AlignmentCenter (flex column)
Max text width320pt
ContainersecondarySystemGroupedBg + border
Typography
Icon48pt, tertiaryLabel
Titletitle3 (20px) semi, Besley
Descriptionsubheadline (16px) secondaryLabel
ActionAny button variant (typically primaryTinted)
When to use
  • .empty-state/No data to show — empty inbox, no search results, first-time user. Replaces blank space with guidance.
  • .empty-state-icon/48pt outline icon in tertiaryLabel. Illustrative, not decorative — the icon should hint at what belongs here.
  • .empty-state-desc/Explain why it's empty AND what the user can do. Max 320pt width for comfortable reading.
  • action button/Optional CTA to resolve the empty state. Use primaryTinted for new actions, tertiary for navigation.
Platform notes
  • iOS ContentUnavailableView (iOS 17+) with .label, .description, and .actions. Matches system pattern.
  • Web <div class="empty-state"> with SVG icon, heading, paragraph, and optional button. Centered in parent.
  • All Keep the message encouraging, not apologetic. "No messages yet" > "Sorry, nothing here". Guide toward action.

Skeleton

Appearance
Backgroundskeleton token
ShimmerquaternarySystemFill gradient
Animation1.8s linear infinite, translateX
Text height14pt (matches footnote line height)
Text gap8pt between lines
Shapes
Textradius6, full-width (last: 60%)
Circle50% radius — avatars
Rectangleradius10 — images, cards, icons
When to use
  • .skeleton/Loading placeholder that mirrors the shape of incoming content. Better than a spinner for predictable layouts.
  • .skeleton--text/Text line placeholders. Stack 2–4 lines. Last line at 60% width creates a natural paragraph end.
  • .skeleton--circle/Avatar placeholder. Match the size of the avatar it replaces (28–64pt).
  • .skeleton--rect/Image, card icon, or media placeholder. Set width and height to match the target content.
  • .skeleton-group/Container that pairs a shape (circle/rect) with text lines. Mimics list item or card layout.
Platform notes
  • iOS Use .redacted(reason: .placeholder) on SwiftUI views for automatic skeleton generation.
  • iOS Custom shimmer: animate a LinearGradient mask across the view with spring timing.
  • Web CSS-only via ::after pseudo-element with translateX animation. No JS required.
  • Web Match skeleton dimensions to real content to prevent layout shift when data loads.
  • Android Use Modifier.placeholder() from Accompanist or custom shimmer with InfiniteTransition. Map --skeleton token color.
  • All Use --skeleton token for background — it's theme-aware and warm-tinted (not pure gray).

Markdown

Getting Started

Building for the long term

Good software is not written — it is rewritten. The best systems evolve through careful iteration, not grand rewrites. Every decision you make today shapes the landscape your future self will navigate.

Core principles

When designing an interface, consider the hierarchy of attention. Users scan before they read, click before they explore, and leave before they understand. Your job is to make each of those moments count.

Design is not just what it looks like and feels like. Design is how it works.

Typography

We use three font families across the system. Each serves a distinct role:

  • Besley — display and titles. A transitional serif with warm, confident character.
  • Figtree — body and UI text. A geometric sans-serif that's clean and readable at every size.
  • IBM Plex Mono — code, data, and labels. Tabular figures, technical precision.

Color tokens

All colors are defined as CSS custom properties and resolve automatically for light and dark themes. The system provides semantic tokens for common use cases like --destructive, --success, and --link.

TokenLightDarkUsage
--label#1A1A18#F5F5F2Primary text
--tint#E86420#F07030Brand accent, CTAs
--link#1270B8#1A8DE0Inline links
--destructive#CE2021#E84142Delete, errors

Implementation

The prose class wraps any block of rendered markdown and applies the full typographic scale. Headers use Besley, body uses Figtree, and code blocks use IBM Plex Mono — all automatically.

// Apply the prose styles to any container
<div class="prose">
  {{ rendered_markdown }}
</div>

// CSS custom property for tint
document.documentElement.style
  .setProperty('--tint', '#E86420');

Inline elements

Inline code like var(--systemFill) uses a subtle tint color. Bold text uses Figtree 600. Italic text uses native italic. Bold italic combines both. Strikethrough marks removed content. Links like color tokens are underlined with a translucent line that solidifies on hover.

Detailed specifications

The h5 heading uses Besley at a smaller size, bridging the gap between h4 and body text. Use it for fine-grained subsections within a longer document.

Revision History

The h6 heading switches to IBM Plex Mono uppercase with letter-spacing — ideal for metadata labels, version tags, and document annotations.

Nested lists

  • Design tokens
    • Color primitives
      • Brand orange #E86420
      • Neutral grays
    • Semantic aliases
  • Typography scale
    1. Display — Besley
    2. Body — Figtree
    3. Mono — IBM Plex Mono

Task list

  • Define color tokens in CSS custom properties
  • Set up font loading via Google Fonts
  • Build typography scale for web
  • Add dark-mode media query fallback
  • Audit contrast ratios (WCAG AA)

Media

Placeholder image demonstrating prose image styling
  1. Define your color tokens in :root
  2. Load fonts via Google Fonts (single <link> tag)
  3. Apply .prose to markdown containers
  4. Toggle themes with data-theme="dark"

This example demonstrates all markdown elements styled with the design language. The same visual language applies whether content is authored in a CMS, rendered from an API response, or written inline.

When to use
  • .prose/Wrap any rendered markdown. Applies full typographic scale — h1–h4 (Besley), body (Figtree), code (IBM Plex Mono).
  • .prose h1/34px Bold Besley. Page-level title. Only one per document.
  • .prose h2/24px Bold Besley. Major sections. Bottom border for visual separation.
  • .prose h3/20px Semi Besley. Subsections.
  • .prose code/Inline: tint-colored, systemFill bg, rounded. Block (pre code): neutral, tertiarySystemBackground.
  • .prose blockquote/3px tint left border, subtle fill, italic. For callouts and quotes.
  • .prose h5/16px Semi Besley. Fine-grained subsections within longer documents.
  • .prose h6/12px Semibold IBM Plex Mono, uppercase, tracked. Metadata labels, revision tags.
  • .prose del/Strikethrough with secondaryLabel color. Marks removed or deprecated content.
  • .prose a/Link color with translucent underline. Hover solidifies the underline.
  • .prose .task-list/Custom checkboxes with tint fill when checked. No bullets, compact layout.
  • .prose img/Full-width, rounded corners, subtle margin. Responsive with max-width: 100%.
  • .prose table/Mono uppercase headers, separator borders, compact padding.
Platform notes
  • Web Apply .prose class to any container with rendered HTML from markdown. Works with any markdown-to-HTML library.
  • iOS Use NSAttributedString with matching font/color attributes. Map h1→title1 (Besley 28pt Bold), body→Figtree 17pt, code→IBM Plex Mono 14pt.
  • iOS For chat messages, ChatKit's MessageAttributedStringBuilder handles markdown→attributed string conversion with these tokens.

Cursor

A blinking insertion point. Two variants: line (thin bar) for text fields, block (filled rect) for terminals.

Line Cursor
Design tokens for iOS
Block Cursor
~/project $ fork status
Line Cursor
Width2px
Height1em (cap-height)
Colortint
UseTypewriter, search, text fields
Block Cursor
Width1 character (en space)
Height1em (cap-height)
Colortint (filled)
UseTerminal, code editor, REPL
Animation
Timing1s step blink (stops during typing)
Keyframescursor-blink (shared)
Reduced motionStatic, no blink
When to use
  • .cursor.cursor--line/Thin 2px bar. Use for typewriter effects, search inputs, and text fields where the cursor sits between characters.
  • .cursor.cursor--block/Filled character-width rectangle. Use for terminals and code editors where the cursor sits on a character position.
  • Both variants use --tint — the cursor is a call to action ("type here"). Same color as the terminal prompt symbol.
  • Both share the cursor-blink keyframes at 1s — one animation across the system. Add .cursor--typing to stop blinking during active input.
Platform notes
  • iOS Line cursor: default UITextField tint. Block cursor: custom CALayer with CABasicAnimation on opacity. Use tint color for both.
  • Android Line cursor: cursorColor = tint on TextField. Block cursor: Modifier.drawBehind with toggled rect via rememberInfiniteTransition().
  • Web Line: ::before pseudo-element with 2px width. Block: background fill on en space (\2002). Both use line-height: 1 for baseline alignment in flex containers.
  • All Respects prefers-reduced-motion — animation stops, cursor stays visible. Blink must be step-based (on/off), never smooth fade.

Dot Indicator

A horizontal row of colored dots. One pattern, multiple uses: tint selection, window controls, status indicators.

Tint Picker
Traffic Light
Tint Picker Dots
Size18 × 18px
Gapspace8
Border2px solid transparent (active: label)
Hoverscale(1.15)
Tap target44 × 44px (invisible ::before)
InteractiveYes — selects tint preset
Traffic Light Dots
Size14 × 14px
Gap6px
ClosesystemRed
MinimizesystemYellow
MaximizesystemGreen
InteractiveDecorative on Web, functional on native
When to use
  • Same pattern, different semantics. The tint picker and traffic lights are both "colored dot rows" — they share visual DNA but serve different purposes.
  • .tint-dot/Interactive selection. Active state shows a --label border ring. Use for picking presets (tints, themes, accents).
  • .window-dot/Status/control indicator. Fixed colors (red/yellow/green) communicate window state. Always show all three.
  • The pattern extends to any scenario with 3–5 colored dots in a row: status indicators, step progress, category markers.
Platform notes
  • iOS Tint picker: use a horizontal HStack(spacing: 8) of tappable circles. Active dot: .overlay(Circle().stroke(Color.label, lineWidth: 2)). Traffic lights: NSWindowButton on macOS, omit on iOS.
  • Android Tint picker: Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) with Box(Modifier.size(18.dp).clip(CircleShape)). Active: border(2.dp, labelColor, CircleShape).
  • Web Both use border-radius: 50%. Tint dots have a 44px invisible ::before for tap targets. Traffic lights are pure CSS with no interactivity.
  • All Minimum 44pt tap target for interactive dots. Decorative dots can be smaller but should remain visually balanced (≥12px).

Progress Bar

Uploading
73%
Complete
100%
Waiting
0%
Progress Bar
Track bgtertiarySystemFill
Fill colortint
Height4px
Radius2px
Animation0.4s easeFluent
Label fontIBM Plex Mono · typeCode
When to use
  • .term-progress/Determinate progress — uploads, builds, scans. Shows label + track + percentage. Use when you can calculate exact completion.
  • .term-progress-bar/The filled portion. Set width via inline style or JS. Transitions smoothly via --easeFluent.
  • For indeterminate progress (unknown duration), prefer a spinner or skeleton — not an animated progress bar.
  • Tint-colored fill ties the progress to the brand action color. This is intentional — progress is an action in motion.
Platform notes
  • iOS Use ProgressView(.linear) with .tint(Color.tint). Track: tertiarySystemFill. Custom height via .scaleEffect(y: 0.5) or GeometryReader.
  • Android LinearProgressIndicator(progress, color = tint, trackColor = tertiarySystemFill). Set Modifier.height(4.dp).clip(RoundedCornerShape(2.dp)).
  • Web CSS transition: width 0.4s var(--easeFluent) handles smooth updates. Update style.width via JS.
  • All Always show a percentage label — users need to know "how much is left." The label uses mono font for fixed-width digits that don't jitter as numbers change.

Window

A framed container with optional chrome. The base surface for terminals, code previews, embedded browsers, media players — anything that needs windowed containment.

fork — ~/project
Window with title bar — single-content mode. Title bar shows traffic lights + centered label. Content goes here.
Preview
Console
Network
Window with tabs — multi-view mode. Tabs replace the title. Active tab has systemFill background.
Chromeless window — no title bar, no traffic lights. Just the framed surface with shadow and radius. Use for embedded content that doesn't need window controls.
terminal
~$fork status
✓ All systems operational
preview.md

Design tokens

FORK uses warm neutrals instead of pure gray. The base label color is #1A1A18, not #000000.

Window Frame
BackgroundsecondarySystemBackground
Border1px solid border
Radiusradius14
ShadowshadowMedium
Overflowhidden
Title Bar
BackgroundtertiarySystemBackground
Border bottom1px solid border
Paddingspace12 × space16
Title fontIBM Plex Mono · caption1
Title colorsecondaryLabel
Traffic Lights
Dot size14 × 14px
Dot gap6px
ClosesystemRed
MinimizesystemYellow
MaximizesystemGreen
Tabs
FontIBM Plex Mono · caption2
Paddingspace4 × space12
Radiusradius6
Active bgsystemFill
Active textlabel
Inactive texttertiaryLabel
When to use
  • .window/The framed container. Provides background, border, radius, shadow, and overflow:hidden. Content-agnostic — put anything inside.
  • .window-titlebar/Optional chrome. Contains traffic lights + title or tabs. Omit for chromeless embedded content.
  • .window-title/Centered mono label. Use for single-content windows (terminal, file preview, media).
  • .window-tabs/Replace title with tabs for multi-view windows. Active tab gets systemFill background.
  • Chromeless variant — omit .window-titlebar entirely. The frame still provides elevation and containment without window controls.
  • Content is free. Terminal puts .term-surface inside. Markdown preview puts .prose inside. Code editor puts syntax-highlighted output inside. The window doesn't care.
Platform notes
  • iOS On macOS Catalyst: traffic lights map to NSWindowButton. On iOS: omit traffic lights, use system navigation chrome (back button, close X). The title bar becomes a plain header.
  • Android Title bar maps to a custom TopAppBar with containerColor = tertiarySystemBackground. Traffic lights are decorative only — use system back/close affordances for real interaction.
  • Web .window uses overflow: hidden to clip child content to the rounded frame. Title is auto-centered via flex + margin offset. Traffic lights are purely visual unless building an Electron/PWA.
  • All The window follows the system theme — no forced dark mode. Traffic lights always show all three dots. Partial sets (e.g., close only) look broken and signal non-standard behavior.