Overlays, inputs & elevation — one surface language

Companion to the button & surface prototype (saga-dash #649) — modal shells, popovers, search inputs, focus, shadows, layering. Flip Existing ↔ Proposed, tune, then Copy preferences to paste on the issue.

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-accent slot, vendor buttons
  • qboard: canvas chrome, presence palette, AV states
Blue
#006298
ALL APPS
Navy
#0B2A4A
ALL — chrome only
Teal
#1ECECA
upstream: “light-blue”
Teal ink
#0D7377
DASH SEMANTIC
Red (clay)
#991A21
DANGER EVERYWHERE
Pink
#FF595A
upstream: “saga-red”
Green
#20912D
SUCCESS
Lime
#6ED34B
upstream: “saga-green”
Orange
#FFAD00
Dark orange
#D14600
ATTENTION-STRONG
Yellow
#F9E547
JANUS WARNING
Magenta
#BB16A3
QTF RAMP
Coral
#FFA38B
Light green
#A7E163
Beige
#D6D1CA
Gray 444
#444444
CANON BODY TEXT

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.

Anders MS ❘ Map External Users to Periods ⓘ
Sections
Algebra 1 — P2
Algebra 1 — P4
Math 7 — P5
Period 2
Algebra 1 — P2
Period 4
Algebra 1 — P4
outlier ×7 program-config family — black scrim, 0.75em radius, blue title + blue header border, full-width step-tab band (grey inactive tabs, blue underline on the active one), 88em board width, StandardButton (4px) footer. Also: DayType ×2, CopyProgram, RecurrenceRemove, StepGroups, RotationRecurrence.
Upload STAR CSV
  1. 1 Import
  2. 2 Map
  3. 3 Review
  4. 4 Finalize
Drag & drop a .csv, or browse
Required: Student ID, Scaled Score, Test Date
outlier ×4 reports CSV wizards — slate scrim rgba(17,24,39,.45) at z-100 (below the modal band), 0 10px 40px shadow. Four byte-identical copies (exit-tickets, STAR, math-grades, surveys).
Upload Roster
✓Upload2Review3Confirm
2 new students · 1 update · 0 errors
outlier roster-uploader — self-labelled “legacy-canonical”: 15px radius, navy scrim at z-100, sole consumer of FWModalMultiStepHeader (the circle stepper).
Anders MS ❘ Map External Users to Periods
  1. ✓ Select students
  2. 2 Create tutoring periods
Sections
Algebra 1 — P2
Algebra 1 — P4
Math 7 — P5
Period 2
Algebra 1 — P2
Period 4
Algebra 1 — P4
Map-enrollments on core-ui. The board keeps its width (Modal needs a 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.
Upload STAR CSV
  1. 1 Import
  2. 2 Map
  3. 3 Review
  4. 4 Finalize
Drag & drop a .csv, or browse
Required: Student ID, Scaled Score, Test Date
CSV wizard on core-ui. One shared step-header snippet replaces the four copied shells; z moves into the modal band. The pill stepper and roster-upload's circle stepper are shown side-by-side in identical chrome so one of the two styles can be picked for the shared snippet.
Upload Roster
✓Upload2Review3Confirm
2 new students · 1 update · 0 errors
Roster upload on core-ui — keeping its circle stepper for the side-by-side comparison with the pill style above.

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.

Program A
Program B
Program C
dominant input popup — 1px #9f9f9f, 6px, 0 4px 12px, z-1500.
9:00 AM
9:15 AM
9:30 AM
outlier TimeField — z-1000 (can land under a stacked modal), border #d9d9d9, unique shadow.
Tutor: Kim
Grade: 7
Pod: 3
outlier FacetedSearch panel — radius 8, third border grey #cfd6dd, z-1000.
Notifications
Settings
Sign out
dominant card menu — bell/user: --color-border, radius-lg, --shadow-lg.
Program A
Program B
Program C
Input popup recipe — border --input-border-color, radius-md, --shadow-popover, z --z-popup (1500). TimeField + FacetedSearch converge here.
Notifications
Settings
Sign out
Card menu recipe — unchanged: --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.

Roster — pill, focus = border swap.
Reports ×4 / FacetedSearch — rect 6px, border --color-border.
Sessions — rect 4px, focus = 2px teal outline.
SearchInput — shape + focus from your controls. Same recipe embeds in SelectMenu and pod-board panels.

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.

As shipped: the title input shows a 2px blue outline inset + border swap, the period trigger a 1px box-shadow ring (SelectMenu), the rotation control a teal outline (chip convention), and the buttons the global 2px blue outline, offset 2. Four spellings in one form.
One token (--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.

Algebra 1 · Pod 3
9:00 – 9:45 · Rm 204 · 4 students
AT JM KL
Session card (sessions page) — live sessions carry the blue left border. Join is the filled elevated button (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.
Set up your program

Add people, build the schedule, and map enrollments. Takes about 15 minutes.

Wizard island (program-config landing) — 1em radius, shipping halo 0 0 6px 4px #0000001a, blue title on white, StandardButton.
Roster summary
Students124
Tutors18
Unassigned6
SagaCard (core-ui) — the --card-shadow token itself is a 0-offset halo (0 0 6px #d9d9d9), echoed raw in QTF report + onboarding.
Nina R. is already booked in Pod 2 for this period. Keep in both✕
Session times on this day come from the day type. Review✕
Toasts (core-ui 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.
--shadow-popoverghost token — referenced, never definedinput popups (new)
--shadow-lgcard menus
--shadow-toasttoasts (ImpactSignal)
--shadow-modaldialogs
1000 modal+ islands at 30, 100/101 (wizards), z-60--z-modal
1100 stacked modal--z-modal-stack
1500 popupTimeField sits at 1000, below its band--z-popup
2000 tooltip+ pod-board inline styles at 10000--z-tooltip

Missing-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.

▦
Fractions on a Number Line
workspace · grade 7
Edit Session → Content row (.content-thumb, 48px).
▦
Fractions on a Number Line
workspace · grade 7
Add-content picker card (.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.