Patterns · composition guide
Settings pattern
How to organise a settings surface — group ordering, help-text vs tooltip, sub-settings, plan-gating, and the danger zone — built from existing components.
◆ generated from guidelines/settings.json + parts/settings.guide.md — do not edit by hand
Based on
The rationale, worked examples, and the full assertion set live in the design skill — this pattern distils the enforceable subset and links each rule back to it.
aha-design:aha-design-settings
judge aha-design:aha-design-settings-judge
Choose the surface
| Surface | Use for | Example |
|---|---|---|
| Inline panel (editor right pane) | per-object settings, immediate effect on canvas | block required toggle, choice count |
| Settings page (route) | workspace/account-level, affects a whole scope | workspace name, billing, permissions |
| Modal | one-time action or short flow, not independently persisted | export options, one-time import config |
| Drawer | supplements a primary view, 3–8 settings | distribution settings, results filter |
Composed of
What a compliant settings surface reuses from this design system — the pattern's link into the component graph.
| Reuses | Kind | For | In DS? |
|---|---|---|---|
checkbox | component | grouped options saved together; consent | available |
button | component | the danger-zone CTA (danger variant → colorError) | available |
icon | component | the ? help glyph, drag handle, per-option image, delete — functional icons only | available |
--aha-size* | token | the whole spacing hierarchy (4/8/16/32/48) — separation comes from spacing, never dividers | available |
--aha-color-error | token | danger CTA colour — never hardcode a hex | available |
switch | component | a single setting that takes effect immediately (toggle rows) | available |
select | component | duration/unit/mode dropdowns; recommended default pre-selected | available |
input | component | text/number fields; NumberWithUnit for a number + unit | available |
tooltip | component | the ? help tooltip (HelpTooltip) — shared dark-navy tooltip, arrow anchored to the ? icon | available |
settings-list | component | the reusable form of this pattern — SectionHeader (semibold group header) + SettingRow (label left / control right, or stacked) rendered from a schema over DS controls; spacing-only hierarchy | available |
dropdown | component | DropdownMenu — themed action menu (aha-dropdown + aha-menu) for image Change/Edit/Delete and Improve-style menus | available |
menu | component | the aha-menu inside DropdownMenu's action menu — neutral-grey item hover | available |
mode-field | component | ModeField — a labelled field whose input swaps with an inline exclusive mode control (outline radio group / segmented); only the active mode's body is live | available |
counted-input | component | CountedInput — single-line field with a focus-only char counter (counter is a persistent sibling revealed via :focus-within, never a remount-on-focus) | available |
counted-textarea | component | CountedTextarea — multi-line sibling of CountedInput; grows between minRows/maxRows, same focus-only counter | available |
card-select | component | CardSelect — single-select as an icon+label card grid; the one selection shape Ant has no built-in for | available |
number-with-unit | component | NumberWithUnit — digit input + hover stepper (clamped, maxDigits) + unit-in-full + optional error line, at an 8px corner; for durations/numbers-with-a-unit | available |
option-row | component | OptionRow — repeatable option/answer row: drag handle, optional correct toggle, borderless textarea with hover counter, optional image control, floating delete | available |
numbered-item | component | NumberedItem — repeatable numbered SECTION wrapper for composite items (chip + '<Label> N' header + hover delete + grey container); body is a slot | available |
question-list | component | QuestionList — editable list of collapsible questions (prompt field + OptionRow choices); composes NumberedItem/OptionRow | available |
image-dropzone | component | ImageDropzone — full-width settings-only image field (dashed card, empty/loading/filled) emitting intents; the host runs the upload modals, never a self-built uploader | available |
image-action-button | component | ImageActionButton — per-option image control with empty/loading/thumbnail states + Change/Edit/Delete menu; emits intents | available |
info-box | component | InfoBox — settings-only tinted callout (information/success/warning/error), optionally dismissible with persisted state; bespoke, not Ant Alert | available |
Rules
The shippable checklist — each rule traces to an assertion in aha-design:aha-design-settings.
| Rule | Skill assertion |
|---|---|
| Setting name is a short noun phrase — no leading verb (Enable/Allow/Show/Turn on). The control communicates the action; the label names what is acted on. | SETTINGS-01 SETTINGS-40 |
| Toggle when the change takes effect immediately; checkbox when options are saved together as a group or express consent. Never mix toggles and checkboxes in one group. | SETTINGS-11 |
| Help text only for a must-see consequence that is hard to undo (≤90 chars, states the consequence). Default is ZERO help lines per panel; everything else is a ? tooltip or nothing. Never both channels on one setting. | SETTINGS-02 SETTINGS-28 SETTINGS-29 |
| The help trigger is a question-mark (?) glyph from the shared Icon — never an info circle (ⓘ). Every ? in a panel is the same glyph, size, and tooltip placement; the tooltip is the shared dark-navy tooltip with its arrow anchored to the icon. | SETTINGS-26 SETTINGS-30 SETTINGS-35 |
| Hierarchy and separation come from --aha-size* spacing tokens (4 name→help, 8 sub→parent, 16 sibling, 32 group, 48 danger zone) — never divider lines, never raw px. | SETTINGS-13 |
| Plain settings and groups sit directly on the panel surface, separated by spacing — never wrapped in a card or tinted/bordered container. Card styling is reserved for genuinely selectable items and the repeatable composite-item wrapper. | SETTINGS-15 |
| A group has 2–6 settings. Header is a 1–3 word noun phrase, sentence case, no colon. Only the group header carries weight — semibold (600, the SectionHeader weight); member/single-control labels are regular weight (400). | SETTINGS-05 SETTINGS-06 SETTINGS-37 |
| Build a settings surface by reusing the canonical composed controls — SectionHeader, SettingRow, SubSettingGroup, HelpTooltip, ModeField, CountedInput/CountedTextarea, NumberWithUnit, CardSelect, OptionRow, NumberedItem, QuestionList, ImageDropzone/ImageActionButton, InfoBox, DropdownMenu — mapped to their DS form (parts/settings.guide.md). Never hand-roll a settings control that already exists in the library; a control the DS does not yet ship is a tracked backlog gap (composedOf), not licence to re-implement it privately. | SETTINGS-15 SETTINGS-33 |
| A dependent sub-setting is HIDDEN (not disabled) when its parent is off. When shown it reads as nested — indented and de-emphasised — never as a top-level peer and never bracketed by its own separators. | SETTINGS-07 SETTINGS-14 |
| A plan-gated setting stays visible but locked (crown badge) — never hidden. The badge opens the shared Paywall popover (owned by aha-design-paywall), not a bespoke one. | SETTINGS-08 |
| Dangerous/irreversible settings sit last, set apart by the largest gap (48). The CTA is the danger Button (colorError, no hardcoded hex); red never on the label. Any irreversible action requires a confirmation modal. | SETTINGS-09 SETTINGS-10 SETTINGS-12 |
| A narrow control sits inline with its label (label left / control right); a wide control (textarea, wide select, radio group, image dropzone) drops below it. Never stack a narrow control or cram a wide one inline. | SETTINGS-47 |
| Every object occupies only its real content height. A container that renders nothing visible is removed or collapsed to zero — never left holding space (the #1 source of editor mystery-gaps). | SETTINGS-19 |
Composition code
Doc-only — this pattern ships no wrapper from this repo. Doc-only pattern — ships no composition wrapper from this repo. The canonical composed settings controls live in slide-type-creator's @/iframe/settings library (ui-standard.json → settingsLibrary is their source of truth); parts/settings.guide.md carries the explicit, one-per-component mapping from each library control to its DS form (a DS contract, a documented convention, or a tracked backlog gap in composedOf). As each backlog control graduates into this DS it gets a contract + reuse block and is gated like a composite; when the DS ships it, flip its composedOf entry from missing→available and move the mapping row to 'mapped'.
Guide
Settings — composition guide
aha-design-settings. The skill holds the full rationale, worked BAD/GOOD examples, and all 52 SETTINGS-xx assertions; this guide is the shippable checklist. When the two ever disagree, the skill wins and this file is regenerated.Settings panels are the most-repeated surface in the product. They reuse existing components — Checkbox, Button, Icon, the spacing scale — so this pattern adds no new control; it defines the conventions that keep every panel reading as one product.
Pick the surface first
| Surface | Use for |
|---|---|
| Inline panel (editor right pane) | Per-object settings, immediate effect on the canvas |
| Settings page (route) | Workspace / account-level settings that affect a whole scope |
| Modal | A one-time action or short flow, not persisted on its own |
| Drawer | 3–8 settings that supplement a primary view |
A single-block panel growing past 8–10 settings is a signal to re-group or split, not to keep adding.
Name and control
- Name = short noun phrase. No leading verb. "Progress bar", not "Show progress bar". The control already says on/off; the label names what is acted on. (Narrow exception: a manual/automatic *mode* toggle may keep its verb — "Show results manually".)
- No decorative leading icon. Panel icons are functional only — drag handle, image, delete,
?help. A trophy on "Leaderboard" is decorative and comes out, however on-topic. - Toggle vs checkbox: toggle when the change takes effect immediately; checkbox when options are saved together or express consent. Never mix the two in one group.
- Placement: narrow control inline (label left, control right); wide control (textarea, wide select, radio group, image dropzone) drops below the label.
Explanation: name → tooltip → help text
This is the single most common AI failure — do not give every setting a description line.
1. Name only — the default; carries most settings. 2. ? tooltip — the home for any "what/why/how it interacts" elaboration. Costs no standing weight. The glyph is a question-mark from the shared Icon (never an info circle), same glyph/size/placement across the panel, shared dark-navy tooltip with the arrow on the icon. 3. Help text — only a must-see consequence that is hard to undo (≤90 chars, states the consequence, ends with a period). Default zero per panel. Never a ? and a help line on the same setting.
Ship gate: count the help-text lines. More than one is almost always the anti-pattern.
SettingRow puts *all* "what/why/how it interacts" elaboration in the ? tooltip — it has no separate always-visible description line. AhaSlides keeps one narrow addition on top of that: the rare must-see consequence help line (tier 3 above), which is near-zero per panel and states a hard-to-undo consequence, not a description. Everything else is a tooltip or nothing. Never run a ? tooltip *and* a help line on the same setting.Grouping — spacing, never lines or boxes
Hierarchy comes from the gap size, on the --aha-size* scale — monotonic so whitespace alone reads as structure:
| Relationship | Token | px |
|---|---|---|
| Name → its help text | sizeXXS | 4 |
| Sub-setting → parent | sizeXS | 8 |
| Between sibling settings | size / sizeMS | 16 |
| Between groups | sizeXL | 32 |
| Danger zone | sizeXXL | 48 |
- Never a divider line between settings, groups, or the danger zone — more distance means a wider gap.
- Never a card/tinted/bordered container around plain settings or a lone toggle; a filled box signals a *selectable* object. Reserve it for real selectable items and the repeatable composite-item wrapper.
- Group shape: 2–6 settings; header is a 1–3 word noun phrase, sentence case, no colon; only the header carries weight — semibold (600) (the
SectionHeaderweight), member/single-control labels regular (400). - Order: most-used first; a dependent sub-setting directly under its parent; dangerous last.
Sub-settings
A setting that only applies when a parent is on is hidden when the parent is off — not shown disabled. When visible it reads as *nested*: indented (sizeLG), tighter gap above (sizeXS), and de-emphasised label — never a top-level peer, never bracketed by its own separators.
Plan-gated and dangerous settings
- Plan-gated: stays visible but locked (crown badge) — never hidden. The badge opens the shared Paywall popover (owned by
aha-design-paywall). - Danger zone: dangerous/irreversible settings sit last, set apart by the largest gap (48). CTA is the danger Button (
colorError, no hardcoded hex); red never on the label. Any irreversible action requires a confirmation modal (peraha-design-overlays) — never a bare toggle flip.
No empty height
Every object occupies only its real content height. A container that renders nothing visible — an empty section, an empty list wrapper, an empty-body settings iframe — is removed or collapsed to zero, never left holding space. This is the top source of editor "mystery gaps".
Mapping the settings-lab library to the DS
The composed settings controls have one source of truth — slide-type-creator's @/iframe/settings library (ui-standard.json → settingsLibrary). This is the canonical, one-per-component mapping from each library control to its AhaSlides-design form. A control resolves to one of three homes: a DS component you reuse today; a DS convention this pattern documents (no dedicated element needed); or a backlog gap — a composite the DS does not yet ship, tracked in composedOf (status: missing) so a fully-compliant surface is buildable by reuse once it lands. Never hand-roll a control the library already defines.
| settings-lab control | DS form | Home |
|---|---|---|
| SectionHeader | <aha-settings-list> group header — semibold (600), optional ? tooltip + action slot | convention (in settings-list) |
| SettingRow | <aha-settings-item> / a settings-list schema row — label left / control right, or stacked | mapped → settings-list |
| SubSettingGroup | *sub-settings* rule — indent-only, hidden (not disabled) when the parent is off | convention |
| HelpTooltip | <aha-tooltip> + <aha-icon> ? glyph (the *help-glyph* rule) | mapped → tooltip |
| DropdownMenu | <aha-dropdown> + <aha-menu> — themed action menu | mapped → dropdown |
| ModeField | *label + inline exclusive mode control (outline radio / segmented), body swaps in place* | mapped → mode-field |
| CountedInput | *single-line field + focus-only char counter (persistent sibling, no remount)* | mapped → counted-input |
| CountedTextarea | *multi-line CountedInput; grows minRows–maxRows* | mapped → counted-textarea |
| CardSelect | *single-select icon+label card grid* | mapped → card-select |
| NumberWithUnit | *digit input + hover stepper (clamped, maxDigits) + unit-in-full + optional error line* | mapped → number-with-unit |
| OptionRow | *repeatable option row: drag handle, correct toggle, borderless textarea + counter, image, delete* | mapped → option-row |
| NumberedItem | *numbered composite-item wrapper: chip + <Label> N header + hover delete + grey container* | mapped → numbered-item |
| QuestionList | *collapsible questions (prompt + OptionRow choices); composes NumberedItem/OptionRow* | mapped → question-list |
| ImageDropzone | *full-width settings-only image field; emits intents, host runs the upload modals* | mapped → image-dropzone |
| ImageActionButton | *per-option image control (empty/loading/thumbnail + Change/Edit/Delete)* | mapped → image-action-button |
| InfoBox | *settings-only tinted callout; bespoke, not Ant Alert* | mapped → info-box |
Backlog controls are real gaps, not licence to re-implement: author each here (contract + lib/ + parts + conformance), then flip its composedOf entry from missing → available and move its row to mapped. Until then, a surface needing one composes the closest DS primitives and follows the rules above.
*Full detail, screenshots, and the slide-type config specifics (§10) live in the
aha-design-settings skill. Self-check any built surface with aha-design-settings-judge and fix every FAIL before shipping.*