One system, three surfaces.
Ristra is a shared shopping list that keeps households in sync, aisle by aisle. This design system is the single source of truth for how it looks and feels in three places: the app (one web client that runs in the browser and inside the iPhone app), the marketing site, and Figma. Every color and dimension comes from one token source, and every component below is documented as it ships in the app today.
Tokens flow downhill
Colors, dimensions, type, and motion live in a single source module. A zero-dependency generator expands the nine brand anchors into tonal ramps, verifies every semantic pairing against a WCAG contrast contract, and emits the file each surface consumes. Nothing downstream is hand-edited.
The app: web and iPhone
tokens.css is copied verbatim into the app and never hand-edited. Since
2.0 (September 2026) the iPhone app is a Capacitor shell around the same web app, so one
stylesheet themes every platform. The few platform differences are CSS dialects keyed off
data-platform on the root element (see App shell).
App-level aliases
Every color the app draws text or glyphs with is a token here, under the contrast
gate. What the app still layers over the tokens in its app.css is layout and
material: the 26 px card radius, the layout margin, control sizes, row padding, glass.
See Spacing & layout and Color.
Marketing site
getristra.com loads the same tokens.css before its own stylesheet; its
dark-field aliases (--ink-*) are earth-ramp references. The brand SVGs and
both display faces ship from this repo's assets/.
Figma
ristra.tokens.json is a W3C-DTCG token file: ramps as primitives, the
semantic set as one collection with light and dark modes. The Ristra Design System
library in Figma was built from these values in July 2026.
Theme.swift and Theme.kt have no consumer; they stay in the
generator, dormant (see Tokens & downloads). The phase-by-phase
record is in MIGRATION.md.
What changed
The decisions this page reflects, newest first. Each one was made and shipped in the app first; this is where the design record catches up.
| Date | Decision | See |
|---|---|---|
| 2026-09-23 | getristra.com moves to Bold & Strung, a Graft kit built on these ramps exactly as this repo emits them: a chili opener with a golden marker, the espresso and sand fields, the system font at Black weight, hand-placed cards. The site's own pages no longer load tokens.css (its /proto pages still do). | Marketing hero |
| 2026-09-23 | Contrast: every text pair in the app passes WCAG AA, measured in the running app in both themes and gated twice (the token contract here, an axe-core check in the app's test suite). The swipe colors, the Finish teal and a new --danger moved into the tokens; Skip It deepened to ochre-600, in-cart text to turquoise-700, secondary text by 0.02 L. | Color, Swipe actions |
| 2026-09-23 | The Font Awesome kit renders here and on getristra.com: a sprite generated from the app's own icon registries replaces the stand-in drawings. | Iconography |
| 2026-09-23 | This styleguide caught up with the shipped app (1.5.0). The launch lockups became canonical here. | Launch & loading |
| 2026-09-21 | What's New: after an update, one centred card of two to four icon rows, shown once per device per version. | Sheets & dialogs |
| 2026-09-20 | Skipped rows leave the aisles for a Skipped card below them. Under a store lens, a long press drags a row within its aisle, and the order is saved per store. | Aisle cards |
| 2026-09-16 | Dark Home paints --bg flat: the paper grain is a light-theme layer. On the dark field it lifted #0f0a06 to #1e1916, too close to the cards. | Home |
| 2026-09-14 | UI pass 1. The native-iOS layout runs on every platform: large-title navigation bar, glass controls, borderless inset-grouped cards, floating buttons. Also: Font Awesome Sharp, the Finish capsule, Staples as its own screen, the seven-step Tour, the launch lockup, and rubber-band scrolling back on in the iOS shell. | Navigation bar, App shell |
| 2026-09-10 | Capacitor: the iPhone app becomes a shell around the web app (store version 2.0.0). The native iOS and Android clients are retired, and SF Symbols with them; the Swift and Kotlin theme files lose their consumers. | Overview |
| 2026-08-24 | 1.2.0: a store is an ordering lens, not a filter. Store tabs and the store chip row are gone; a lens row names the order the aisles follow. | Aisle cards |
| 2026-08-03 | 1.1.0: the list runs on swipes. Got It, Skip It and Delete replace the check circle. | Swipe actions |
| 2026-07-24 | The Figma library is built from these tokens: variables, text styles, icons, components. | Overview |
| 2026-07-10 | Design system v1: tokens, generator, contrast gate. Brand v4 (vivid anchors, split icon canonicality), the 975 near-black step, chip tokens and the grain texture. | Color |
Logo & lockups
The mark is a ristra — four chilies strung together, one per household member pulling the same list. It hangs on one of two brand fields: sand in light contexts, earth in dark ones. Variant names describe the ink, not the background: use the light-ink files on earth, the dark-ink files on sand.
Full-color lockups
Launch lockup: mark and wordmark, no tagline
Approved 2026-09-14 for the launch screen and the web loading screen (see Launch & loading). Derived from the full-color lockups: tagline paths removed, the wordmark moved down to centre on the mark, the viewBox cropped with equal padding. Same ink naming. These two files are the canonical source; the app keeps verbatim copies.
Monochrome lockups
For single-color contexts: embossing, watermarks, partner co-branding, or anywhere the full palette would fight the surrounding design.
Icon tiles & bare mark
ristra.icon) — glass materials over the vivid brand anchors — is
canonical for the iOS app icon only. Every web and marketing icon surface
(favicons, PWA set, touch icons, site marks) rasterizes from the flat tiles
icon-light.svg / icon-dark.svg in this repo. In the app, the
stacked mark itself is the logo, and it also watermarks Home at 5% opacity; the old v2 line
chili survives only as decorative illustration.
Wordmark & tagline
Two display faces, two jobs, and they never trade. Solitreo sets the wordmark — and only the wordmark. Jua sets the tagline and short display moments on marketing surfaces. Neither ever sets body copy; body and UI text are always the native system stack.
Live specimens
| Face | Token | Use for | Never for |
|---|---|---|---|
| Solitreo | --font-display | The wordmark "Ristra" | Anything else — headings, UI, body |
| Jua | --font-tagline | Tagline; short marketing display lines | Body copy, UI labels, in-app text |
| System stack | --font | All body and UI text on every platform | — |
| Mono stack | --font-mono | Code, token names, quantity and count pills | Prose |
Clearspace & minimum sizes
Give the ristra room to hang. Keep a clearspace of one chili-height (¼ of the mark's height) on all sides of any lockup, and never let other elements cross it.
| Asset | Size | Notes |
|---|---|---|
| Bare mark | 24 px tall minimum | Below that, use the icon tile instead |
| Full lockup (mark + wordmark + tagline) | 160 px wide minimum | Drop the tagline below 220 px |
| Launch lockup (mark + wordmark) | 240 pt wide, fixed | Launch and loading screens; at phone widths a tagline would set about 15 pt tall, so it stays off |
| Wordmark alone | 72 px wide minimum | — |
| Icon tiles | 16 px minimum | Favicon floor |
Don'ts
- Don't recolor the chilies outside the four brand body colors.
- Don't set the wordmark in any face but Solitreo, or the tagline in any face but Jua.
- Don't place the dark-ink lockups on earth, or light-ink lockups on sand.
- Don't rotate, outline, shadow, or add effects to any lockup.
- Don't use the app-icon glass palette in UI or marketing surfaces.
Launch & loading
The first thing anyone sees. The launch screen shows the launch lockup 240 pt wide,
centred on --bg in both appearances. The web loading screen draws the same
lockup at the same size and position, so the hand-off from the native launch to the app
doesn't move.
- It follows the phone's appearance, not the in-app theme. A launch screen can't read the app's setting, and the only override would force the keyboard and system sheets into one mode too.
- The loading screen paints in its first frame: it inlines the SVG rather
than loading an image. If loading runs past three seconds, "Loading your list…" fades in
24 px below the lockup in
--text-muted. - iOS gets PNGs at @2x and @3x, generated from these SVGs and
tokens.css; an SVG in a launch image view has black-screen reports. Never set a splash background color in the shell's config: it paints one color over dark mode.
Color
Nine brand anchors — the four chili bodies, the stem, the two fields, and the two wordmark turquoises — expand into seven tonal ramps. Anchors are brand and illustration colors; interface color always comes from the semantic tokens, which are locked to contrast-safe ramp steps by a generator-enforced WCAG contract.
Brand anchors
Values are verbatim from the Figma brand file and the SVG assets. Anchors flagged brand only fail AA as text on their field — use their ramp's deeper steps in UI instead. Click any swatch to copy its hex.
Tonal ramps
Twelve steps per ramp, 50→975, built on an OKLCH lightness ladder. The dot marks the step where the brand anchor survives verbatim. The earth ramp doubles as the warm-neutral ladder: sand sits at 100, earth at 950, and every surface, border, and text tone between them is brand-derived. The 975 near-black exists for dark-mode backgrounds — in dark themes, cards sit at earth-950 (the brand field) on an earth-975 page.
Semantic tokens
The interface vocabulary. Names match the app's custom properties exactly, so a value change here is a value change there. Status colors are text colors (item names), so they carry the 4.5:1 text contract; the swipe colors are the same in both themes. Live values follow the page theme — flip the toggle up top and watch them re-resolve.
| Token | Light | Dark | Role in the app |
|---|
Materials (app-level)
The app layers these over the tokens in its app.css. None of
them carries text: every color text or glyphs are drawn in is a semantic token above.
| Alias | Resolves to | Used for |
|---|---|---|
| --separator | --border mixed 60% into --surface | Inset row hairlines |
| --glass-bg | --surface at 72%, blur 16, saturate 160% | Glass circles and capsules, glass floating buttons |
| --bar-material | --bg at 80%, blur 20, saturate 180% | The navigation bar, only once content scrolls under it |
| --glass-shadow | 0 4 24 earth at 10% · dark: black at 50% | Glass and floating controls |
Contrast, proven live
Every pair in the contrast contract, read from the generated token file: each swatch draws its actual token pair and computes the WCAG ratio in your browser. The generator enforces the same minimums at build time — a failing pair can't ship. Pairs held to 3:1 are large text or glyphs, so their sample is set large. The app's test suite then checks, with axe-core, that every screen uses these pairs as intended.
Light theme
Dark theme
Typography
Body and UI text is always the platform's native stack: SF on Apple platforms, Roboto on Android. Zero font payload in product surfaces; the two brand faces appear only where the brand rules allow.
Token scale
The general-purpose ladder: settings screens, forms, this page, and the marketing site.
Interface text styles
The native-iOS layout (2026-09-14) sets its text at the iOS text-style sizes: Large Title 34, Body 17, Footnote 13. They apply on every platform, as literals in the components named.
| Style | Size / line | Weight | Color | Where |
|---|---|---|---|---|
| Large title | 34 / 41 | 700 | --title | Screen titles; they scroll away (NavBar) |
| Bar title | 17 / 22 | 600 | --title-small | Fades into the bar once the large title passes under it |
| Row title | 17 / 22 | 500 list · 600 Home | --text | Item rows (ItemRow), list rows (Home) |
| Dialog title | 21–24 | 800 | --title | Tour, What's New |
| Section header | 13 / 18 | 700 | --text-muted | Aisle headers, sheet titles; the lens row at 600 |
| Subtitle and note | 13 / 18 | 400 | --text-muted | Freshness line, row notes (one line, ellipsis), Home meta |
| Band | 13 / 18 | 700 | --trip-text | The trip band |
| ×2 | 12 / 16 mono | 400 | --text-muted on --pill-bg | Quantity pills, aisle counts |
| 12 | 28 / 34 rounded | 700 | --text | Home shortcut counts (tabular figures) |
--text-base is the floor for form controls, including the Staples
screen's field (raised from 15 in September 2026).
Spacing & layout
A twelve-step scale for general spacing, plus the layout metrics of the native-iOS layout the app adopted on 2026-09-14. Those were measured against the retired native app and are pinned at 402 pt (iPhone 17 Pro) by the app's metrics suite, within 0.5 px.
Layout metrics
| Alias | Value | Rule |
|---|---|---|
| --edge | 16 · 20 at ≥ 414 wide | The iOS layout margin. Cards, the nav bar, and the floating buttons inset by it. |
| --row-pad-x | = --edge | Row content insets by the layout margin, so row text, separators, and Home icons line up with it. |
| --row-pad-y | 15 | 15 + 22 + 15 = a 52 px single-line row. Home rows add 2 each side: 17 + 42 + 17 = 76. |
| --nav-bar-h | 44 | Sits under the safe area: --nav-top = max(safe top, 8), so 62 + 44 = 106 on an iPhone 17 Pro. |
| --control-size | 44 | Glass circles and the buttons inside capsules. A capsule of two is 6 + 44 + 3 + 44 + 6 = 103. |
| --float-size | 48 | Floating buttons. |
| --float-bottom | max(safe bottom − 6, 16) | 28 above the bottom edge on an iPhone 17 Pro. |
| --float-clearance | --float-bottom + 48 + 24 | The room a scrolling screen leaves under its last row so it clears the floating buttons. |
| Section rhythm | 26 above a header, 10 below | Card to card through an aisle header measures 56. |
Widths: the floating bar caps at 640 px and centres, so on a desktop
window the cart and the + stay near the list; sign-in and join forms cap at 440 px. The
app respects device safe areas through the --safe-* insets.
Radius & elevation
Soft, and flat by default. Inset-grouped cards take the iOS 26 corner (26 px) with no
border and no shadow: the --surface fill alone separates them from the
--bg page. Shadow is for things that float or lift.
26px · fill only
14px
9px
999px
lifted
floating
| Radius | Where |
|---|---|
| 26 · --group-radius | Inset-grouped cards: aisles, Skipped, Complete, Home's list group. Also a row or aisle lifted mid-drag. |
| 22 | Centred dialogs (Tour, What's New) |
| 20, top corners | Bottom sheets |
| 14 · --radius | Home shortcut tiles, Home banners, dialog buttons, settings-screen cards |
| 12 | Icon tiles (Home rows, What's New), menus |
| 9 · --radius-sm | Inputs, form buttons, theme chips |
| 999 · --radius-pill | Capsules, the Finish buttons, pills and badges, the composer field |
| Layer | Elevation |
|---|---|
| Cards on the page | None. Fill only. |
| Glass controls, floating buttons | --glass-shadow |
| A row or aisle lifted mid-drag; menus; the hover quick-acts; settings-screen cards | --shadow |
| Bottom sheets | 0 −8 40 rgba(40,22,12,.28) over a 45% scrim |
| Centred dialogs | 0 20 60 rgba(40,22,12,.4) over a 55% scrim |
Motion
Quick and settled — rows glide, sheets rise, nothing wobbles. Three durations, two curves. Click a chip to play its timing.
| Movement | Timing |
|---|---|
| A swipe settling open, closed, or out on commit | 200ms --ease-out |
| Arming: the action circle stretches into a pill | 150ms --ease-out |
| Neighbours sliding aside during an aisle or row drag | 150ms --ease-out |
| The bar title fading in | 150ms --ease-out |
| Sheet rise · scrim fade · dialog pop | 200ms · 150ms · 180ms ease |
| Trip dot pulse | 2s ease-in-out, looping |
- Reduced motion: every movement above has an instant or still fallback. The Tour's swipe step holds one still frame with both swipes open.
- Reduced transparency: glass and the bar material flatten to solid
--surface/--bg, and Home's texture layers hide. - The rubber band is scroll physics, not animation. The iOS shell keeps
the native bounce, which shows
--bgpast the edge; it locks while any sheet or dialog is open.
Iconography
One family, two weights, every surface. Ristra draws Font Awesome Sharp from its licensed kit: Sharp Regular for interface chrome, Sharp Solid for aisle and list-type glyphs. Weight is the only thing separating the two roles. The app bundles its glyphs so they render offline; this page and getristra.com load a generated sprite of exactly the same glyphs, so all three always agree.
Interface chrome · Sharp Regular (fasr)
| Glyph | Key (UI.*) | Font Awesome | Role |
|---|
Sizes: 16–20 in nav controls, 18 on floating buttons, 13 in aisle headers
and the lens row, 14 in quick-acts, 22 in What's New tiles. Chrome glyphs take
--accent inside glass controls; the back chevron reads as text.
List-type icons · Sharp Solid (fass)
A list's type picks its default icon; people can choose any of these from the picker. The household document stores only the key.
@awesome.me/kit-f4cb7e0690) is
licensed for Ristra's own surfaces: the app, this page, and getristra.com. The two sites
load assets/icons/ristra-icons.svg, which the app repo generates
(scripts/gen-design-icons.mjs) from its own icon registries: only the glyphs
the app uses, never the library, and never a glyph the app doesn't use for that meaning.
The repos that commit the sprite stay private.
Departments
Twenty-six aisles with stable keys: fourteen in the default grocery walk, eleven specialty aisles that appear only through a store's department set (hardware, garden, and the like), and Other. Each gets a Sharp Solid glyph, rendered muted in its aisle header; departments never get their own accent colors.
| Glyph | Key | Label | Font Awesome | Set |
|---|
Buttons
Shape follows placement. Form actions on a page or sheet are rectangles; the button that closes a dialog spans it; anything in a bar or floating over the list is a circle or a capsule. Accent is the default action color. Teal is reserved for finishing a trip.
Form and dialog buttons
Bar and floating controls
Text actions
- Teal means finish.
--finish-bgis used by the Finish capsule, the in-list Finish shopping button, and the Complete count, and nowhere else. It passes AA in both themes (4.59:1 light, 8.1:1 dark). - The Finish capsule's name is exactly its label. It has no
aria-label, so assistive tech hears "Finish", the word on screen. - Focus rings are a 2 px
--titleoutline, offset 2 px (drawn inward inside clipped cards). - Destructive text and buttons (delete a list, delete an account) use
--danger: chili-700 in light, chili-400 in dark, with--on-dangerfor a label on the fill. It replaced a hard-coded red that measured 2.34:1 in dark.
Inputs & forms
Quiet fields on surface with hairline borders; focus is a turquoise ring and the border steps aside. 16px minimum font size, always. Quick add is the one pill-shaped field: it's a composer, not a form.
Pills & badges
Counts and quantities ride the sand pill in mono. Status pills sit in the nav bar. The one teal badge counts what's in the cart.
--chip-active-bg / --chip-active-text are
now aliases of --finish-bg / --finish-text, --chip-bg
fills the trip-complete seal, and --chip-border / --chip-text are
unused.
Toggles
Choices of three are equal chips with the pick filled in accent — the theme picker in app settings is the canonical instance. On/off settings are full-width rows with a dot that fills. Try both.
List item row
The flagship component — the row a household touches a hundred times a week. No checkbox: the name leads, a tap opens the editor, and swipes do the rest. A single-line row is 52 px, with an inset hairline between rows.
In an aisle
In the Complete card: bought, then bought while a trip is live
In the Skipped card
| State | Looks like | Written as |
|---|---|---|
| Needed | --text name on --surface | needed |
| Bought | Struck through in --status-bought (= --text-muted) on --trip-wash; the quantity pill turns teal (--finish-*) | bought |
| In cart | A bought row while a trip is live: --status-incart (turquoise-700) name, no strike | Render-only; still bought |
| Skipped | --status-unavailable name, in the Skipped card. Outside that card it adds a "Skipped this trip" tag. | skipped (legacy: unavailable) |
- Anatomy: name 17/22 medium; quantity pill when above 1; a coupon glyph
in
--accent-strong; one line of note (or the photo read) that ellipsizes rather than growing the row; a 48 px photo thumbnail at the trailing edge, radius 8. - Press: the row goes
--surface-sunkenwhile a finger is down. A tap opens the edit sheet. - Long press under a store lens lifts the row to drag within its aisle (see Aisle cards).
Swipe actions
Completion is a leading full swipe; skipping is a trailing full swipe at the screen edge; Delete sits inboard and is tap-only. A destructive action never rides a flick.
Leading edge open · Got It
Trailing edge open · Delete (tap only) and Skip It
A taller row past the commit point: labels show, the circle arms
A bought row: Put Back leads, Delete trails
Mouse and keyboard: quick-acts on hover or focus
| Edge | Needed rows | Bought and skipped rows |
|---|---|---|
| Leading (swipe right) | Got It, full swipe commits | Put Back, full swipe commits |
| Trailing (swipe left) | Delete (inboard, tap only) + Skip It (at the edge, full swipe commits) | Delete (tap only) |
- Feel: the row captures after 10 px of travel; a full swipe commits past 55% of the row's width or on a fast fling; open states snap to 84 px per action. The left 24 px are a dead zone, left to the system's back gesture. One row is open at a time, and a tap anywhere else closes it.
- Compact rows: under 64 px tall there's no room for circle and label, so the action goes icon-only on a 30 px circle, the way Mail does.
- Never gesture-only: on a mouse or keyboard, hovering or focusing a row reveals the same actions as 28 px buttons.
- The write lands after the exit animation, so the row finishes leaving before the list re-sorts.
Aisle cards
Items group into borderless inset-grouped cards, one per aisle, in the walking order of the chosen store. Each header sits on the page above its card and scrolls with it. Headers are wayfinding, not content: they stay quiet.
Produce
2Dairy & Eggs
1Skipped
1Complete
2Saves the trip & clears 2 checked-off items · skipped items and staples come back
- The lens row names the order: "Aisles ordered for {store}" or "Default aisle order", with Change opening the store sheet. The choice is per person and per device. During your own trip it locks to a faint label, so the order can't shift mid-shop.
- Header: Sharp Solid glyph at 13 px, the label in sentence case (13/18 bold), a count pill, and, only under a store lens, a grip. Drag the grip to reorder aisles for that store.
- Rows reorder too: under a store lens, long-press a row and drag it within its aisle. The order is saved per store by item name, so it survives items being cleared and re-added every trip.
- Order down the page: aisles, then Skipped (after the last aisle, so aisle counts show only what's left to get), then Complete, then Finish shopping.
- Skipped wears the skipped color on its glyph and count. Complete
collapses, fills with
--trip-wash, and tints its hairlines teal.
Home
Your lists in one inset-grouped card, a shortcuts dashboard under it, and two floating buttons: + for a new list, Join for a shared one. Home is the one screen with texture.
Flip the page theme: the grain shows only in light
- List rows are 76 px: a 42 px icon tile (radius 12, the list's
Sharp Solid glyph in
--accenton--surface-sunken), the name at 17/22 semibold, members at 13/18 muted, a faint chevron. The hairline starts at the text, not the tile. - Shortcuts are two-column tiles in the Reminders shape: a round glyph top left, the count large top right, the title under them. Tapping Staples asks which list first when you have more than one.
- Texture is decoration only. Paper grain at 35% in light only (on the dark
field it lifted the page to
#1e1916, too close to the cards, so dark Home paints--bgflat), and the brand mark oversized at 5% in both themes. Both are click-through, and both hide under reduced transparency.
Sheets & dialogs
Choices rise from the bottom as sheets. Moments that need a pause — the Tour, What's New — are centred cards. Both dim the page, trap focus while open, and close on Escape.
Bottom sheet · the store sheet in its "start" mode
Centred dialog · What's New, with the 1.5.0 notes
| Aspect | Bottom sheet | Centred dialog |
|---|---|---|
| Used for | Store choice, the edit sheet, share, quick add, list and department pickers | Tour, What's New |
| Shape | --surface, 20 px top corners (quick add: 18), 40 x 5 grab handle in --border | --surface, 22 px, up to 420 wide, 16 from each edge |
| Scrim · entrance | 45% · rises over 200ms | 55% · pops over 180ms |
| Title | 13/700 --text-muted (pickers) or 18/700 --title (Share) | 21–24/800 --title, with a 13/600 muted eyebrow on What's New |
| Options | 16 px rows, 13 x 12 padding, radius 10 on press; a tick in --accent marks the current one | What's New: 44 px glyph tiles on --pill-bg, glyph in --accent, 16/700 title, 14.5 body in --text-muted |
- Every overlay marks itself (
data-modal) so the iOS page bounce locks while it's open. - Dialogs own the Tab key while open: focus moves to the title on open, stays inside, and returns to whatever opened it.
- The Tour is the same centred card as a seven-step carousel: add, photo, staples, store lens and aisle drag, swipes, Finish, share. Each step has art on a 376 x 150 slot (--surface-sunken, radius 16) drawn in tokens with the real glyphs, dots in --border with the current one in --accent, Skip top right, and "Let's go" on the last step. It shows once per device.
- What's New shows once per device per version, after an update, never on a first run (the Tour shows then). Settings > Help reopens it.
- Confirming a finish uses the system alert, not a custom dialog.
Menus
Trip & status
A live trip is the list's loudest state and it's still quiet: a sage band pinned under the nav bar with a pulsing dot. Connection state is a small pill beside the nav controls, never a banner.
Your trip
Someone else's trip
Connection, beside the nav capsule
Home cards
- The band is 34 px: 13/18 bold
--trip-texton--trip-wash, a--trip-borderhairline drawn as an inset shadow so it adds no height, and a 7 px--trip-dotpulsing every 2 s. - End vs Finish. End (your trip only) cancels and leaves every item as it is. Finish, on the capsule or under Complete, saves the trip and clears what you got.
- Offline reads the device's connection, not the sync layer's cache flag, which reads "not confirmed" far too often to mean offline. Saving… appears while local edits wait to sync.
- The freshness line under the large title says who changed the list last and when: the trust signal of a shared list.
- Home cards (install on the web, update in the app) are --surface tiles at radius 14 with no border.
App shell
One layout on every platform since 2026-09-14: the retired native iOS app's. The page is
--bg; a large-title nav bar sits over it; aisles are inset-grouped cards at the
layout margin; three floating buttons hover at the bottom with no dock behind them. The
document itself is the scroller.
Before a trip: large title, the lens row with Change, the cart starts shopping
Your trip: title in the bar, the band pinned, the lens locked, the cart becomes Finish
| Part | Rule |
|---|---|
| Page | Natural document flow on --bg: no fixed shell and no inner scroller, so safe-area insets report correctly on iOS. |
| Nav bar | Sticky; transparent at rest; the trip band pins inside it. |
| Cards | Inset by --edge; the next header's 26 px top padding is the gap between cards. |
| Floating buttons | Fixed, capped at 640 wide and centred. Left: the cart starts a trip, becomes Finish during yours, and hides during someone else's. Right: add by photo, then + (accent). The list keeps --float-clearance under its last row. |
| Back | Goes to the screen underneath, whichever way you came: a screen with two ways in (Staples) can't hard-code its back target. Opening a screen starts at the top; going back restores where you were. |
Platform dialects
The root element carries data-platform (web,
ios, and later android), set before first paint. Only iOS has a dialect
today.
| Dialect | iOS shell | Web |
|---|---|---|
| Overscroll | Rubber band on, showing --bg; locked while an overlay is open | Off (no pull-to-refresh mid-list, no trackpad bounce) |
| Glass rim | A 1 px top highlight (white at 45%) on glass controls, as iOS draws its own | None; it would read as a stray border |
--surface top bar with a 28 px title, and 14 px cards with
--shadow.
Members
Everyone here can add, check off, and finish.
Marketing hero
Marketing surfaces lead with chili. getristra.com is built on Bold & Strung, a Graft kit
(~/Sites/Graft/kits/bold-and-strung) that takes these ramps exactly as this repo
emits them and casts them into three color fields, with heavy system type, cards set down by
hand and one marker per headline. The hero at the top of this page is an older, darker cousin.
--terra-700 (sand type at 6.6:1) with two --terra-800 orbs · the header
lies over it: the app icon and the Solitreo wordmark in sand, a sand button · an overline in
--terra-200 · the headline in the system font at 900, tight tracking, its second
line stepped in, and one golden marker (--ochre-300 behind --earth-950
type) · a sand primary button and a sand outline · the dark-mode app capture in an espresso
bezel, turned 1.5° · below it the page alternates the espresso field (--earth-950:
the screenshots, the footer) and the sand reading field (--earth-50, turning
--earth-975 in the dark theme, with a 2px seam line where it meets espresso) ·
Solitreo sets only the wordmark, and Jua is not used · every pair in the kit's contract
(276, both themes) passes AA, and axe checks the rendered pages.
Tokens & downloads
Everything on this page's color sections is generated from one source module. Regenerate with
node scripts/gen-tokens.mjs — the contrast contract runs on every build and
refuses to emit failing tokens.
tokens.css
CSS custom properties, light + dark. Copied verbatim into the app
(src/tokens.css) and getristra.com; never hand-edited downstream.
ristra.tokens.json
W3C DTCG format. In Figma: Tokens Studio → import → sync ramps and both semantic modes as variables.
tokens.resolved.json
Machine-readable resolved hex map — what this page's color sections render from.
ristra-icons.svg · .json
The app's Font Awesome Sharp glyphs as a sprite (<use href="…#ui-check">),
and the manifest this page's icon tables render from. Generated in the app repo from its
own icon registries; licensed for Ristra's sites.
Brand SVGs
Lockups, the launch lockups, icon tiles, the mark, wordmark, and tagline in
assets/brand/. The app copies the ones it needs verbatim into its
brand/.
Theme.swift · Theme.ktDormant
The native iOS and Android theme files. Both clients were retired for Capacitor shells on 2026-09-10, so nothing consumes them; the generator still emits them in case a native target returns.
| Resource | Where |
|---|---|
| Migration record (web → iOS → marketing → Figma → Android → Capacitor) | MIGRATION.md |
| Figma brand file (the nine anchors originate here) | Ristra-branding |
| Token source of truth | tokens/tokens.source.mjs |
| App-level aliases (mirrored on this page) | the app's src/app.css, top block |
| Brand SVGs & subset webfonts | assets/brand/ · assets/fonts/ |
Ristra · a Longbrook Studio project · getristra.com · app.getristra.com