Foundations: Tier 1 brand vs Tier 2 app semantics
Every Saga web app re-declares the same palette by hand today (dash, staff-admin, janus, qboard, coach — plus the Figma-exported design-tokens repo nobody consumes). The proposal: publish Tier 1 once, and each app keeps a thin Tier 2 that gives those primitives meaning.
Tier 1 — shared brand primitives (publish once)
- Named colors below + gray ramp — what Saga blue is
- Montserrat + the 10–48px type ladder, numeric weights
- Spacing / radius / border scales
- Motion durations + easings + reduced-motion contract (#627)
- Elevation ladder (shadows, scrims)
Tier 2 — per-app semantics (each app owns)
- What blue means here: primary/confirm, link, active tab
- dash: severity surfaces, soft-blue action hover, column-active teal, pod/rotation palettes
- staff-admin: env marking, on-navy status tints, inline-confirm
- janus: whitelabel
--brand-accentslot, vendor buttons - qboard: canvas chrome, presence palette, AV states
Names use coach's corrective aliases — the Figma export mislabels several hues (its “saga-red” is the Pink, its “saga-light-blue” is the Teal), and dash/janus both carry --color-brand-teal that is actually the Blue. Tier 1 is where those names get fixed once.
Modal shells
~15 hand-rolled shells ship in three style families; core-ui Modal has exactly one consumer. Below are miniatures of three real modals with their real structure — the map-enrollments board with its full-width step-tab band, a reports CSV wizard with its pill stepper, roster-upload with its circle stepper. Proposed shows the same content in the core-ui shell (scrim, radius and header accent from the controls). For the map-enrollments wizard, the Wizard stepper placement control compares keeping the tab band, folding compact steps into the header row (these modals are chronically short on vertical space), or a pills row in the body.
Sections
Period 2
Period 4
- 1 Import
- 2 Map
- 3 Review
- 4 Finalize
Required: Student ID, Scaled Score, Test Date
FWModalMultiStepHeader (the circle stepper).- ✓ Select students
- 2 Create tutoring periods
Sections
Period 2
Period 4
size="board"). The stepper renders per the placement control: the classic tab band, compact steps folded into the header row (no extra row spent), or a pills row in the body. Footer moves onto the standard #f9fafb bar with 6px buttons.- 1 Import
- 2 Map
- 3 Review
- 4 Finalize
Required: Student ID, Scaled Score, Test Date
Popovers & menus
Two legitimate recipes exist — the input popup (SelectMenu/DatePicker) and the card menu (bell/user menus). TimeField, FacetedSearch and DayDetailPopup each drift on border, shadow, radius or z.
--color-border, radius-lg, --shadow-lg.--input-border-color, radius-md, --shadow-popover, z --z-popup (1500). TimeField + FacetedSearch converge here.--color-border, radius-lg, --shadow-lg. Two recipes total, both tokenized.Search inputs
12+ bespoke search fields in three shapes and three focus styles. Proposed: one core-ui SearchInput (leading icon, token border, one focus treatment) — pick its shape and focus in the controls, then tab into the proposed field to feel it.
--color-border.Focus affordance — when it shows, and where
When: the ring is a :focus-visible style — it paints when focus arrives via keyboard (Tab / Shift-Tab), and stays invisible on mouse clicks for buttons (text inputs show their focus on click too, by design). A global 2px rule already exists in the shell; the problem is that ~56 of 86 component overrides re-spell it seven different ways, next to teal/white/navy one-offs and two other treatments. Try it: click into the form below, then Tab through the controls.
--color-focus-ring) + one treatment from your controls, consumed by the existing global rule; components stop re-spelling it. Inputs keep an inset offset so the ring doesn't collide with adjacent fields.Layering gallery — real components at each level
Shadows stay in this proposal. The dashboard has always used layering — cards float over the page, popups over cards, toasts and modals over everything — and the brand's flat/solid rule is about fills and edges, not about abolishing elevation. The open question is coherence: resting cards currently use three different zero-offset halos (session card 0 0 4px 2px, wizard island 0 0 6px 4px, --card-shadow 0 0 6px) while overlays use directional shadows. The Card elevation control compares halo-as-is against one soft directional shadow or border treatments — overlays keep their ladder shadows in every option.
0 4px 4px, kept per the approved #649 elevated variant); Observe is bordered when the card is expanded — today it carries the same drop shadow (visible in Existing; the #649 outlier) — and a soft borderless button when collapsed. Shipping card shadow: 0 0 4px 2px.Add people, build the schedule, and map enrollments. Takes about 15 minutes.
0 0 6px 4px #0000001a, blue title on white, StandardButton.--card-shadow token itself is a 0-offset halo (0 0 6px #d9d9d9), echoed raw in QTF report + onboarding.ImpactSignal) — the consolidated notice/warning surfaces: severity background + border, radius 6, --shadow-toast, underlined blue remediate-in-place action. In-context (not corner-stacked); they keep this shadow in every card-elevation option.--z-modal--z-modal-stack--z-popup--z-tooltipMissing-content thumbnail
The content row inside Edit Session (48px tile, left) and the Add-content picker cards (110px face, right) show a placeholder whenever a workspace has no thumbnail. It is currently a navy→blue soft gradient with a white ▦ (“no preview”) glyph — the app's one true soft gradient, against the hard-edge brand rule. Select an option below; both demos update.
.content-thumb, 48px)..picker-card-thumb, 110px face).The mosaic option reuses the Current Sessions “no preview” art (brand guide v3 pp. 29–30, SessionThumbPlaceholder), seeded per content id — each card differs but stays stable across refreshes.