Patterns · Settings
Setting group
The settings GROUP container — an optional semibold header (with a "?" help and an action slot) over a column of rows — that bakes in the panel spacing scale so gaps come out right automatically.
Examples
Header
Help
Tone
Spacing scale baked in · rows 16 · between groups 32 (automatic) · danger zone 48 · optional header + "?" help + action slot · no dividers/cards
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-setting-group> is the SAME shared custom element React and Vue consume — here in its
native form. It BAKES IN the settings-panel spacing scale: rows 16px apart, groups 32px apart
automatically, a danger zone 48px down — no dividers, no cards, white bg. Theming comes only
from the --aha-* tokens in tokens.css. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // <aha-icon> — the header "?" help glyph + row controls
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-setting-group.js'; // registers <aha-setting-group>
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-sub-setting-group.js'; // registers <aha-sub-setting-group> (the nested cluster)
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-settings-list.js'; // <aha-settings-item> rows (reused for the labelled rows)
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-switch.js'; // reused row control
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-input.js'; // reused row control
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-button.js'; // the danger-zone CTA (danger styling lives on the button)
</script>
<!-- A mini settings panel: TWO groups → the 32px between-group gap is automatic (you set nothing),
and the first group has a row with a nested sub-setting-group (8px above + 24px indent). -->
<div style="max-width:380px">
<!-- Group 1 — an optional header (semibold) + a "?" help; rows sit 16px apart on their own. -->
<aha-setting-group label="Presentation" help="How the deck shows to the audience.">
<aha-settings-item label="Progress bar">
<aha-switch slot="control" checked></aha-switch>
</aha-settings-item>
<!-- A parent toggle + its dependent settings, nested + hidden when the parent is off. -->
<aha-settings-item label="Question timer">
<aha-switch slot="control" id="timerToggle" checked></aha-switch>
</aha-settings-item>
<aha-sub-setting-group id="timerSub">
<aha-settings-item label="Seconds per question">
<aha-input slot="control" value="30"></aha-input>
</aha-settings-item>
<aha-settings-item label="Auto-advance">
<aha-switch slot="control"></aha-switch>
</aha-settings-item>
</aha-sub-setting-group>
</aha-setting-group>
<!-- Group 2 — no wiring: it just sits 32px below Group 1 automatically. -->
<aha-setting-group label="Audience">
<aha-settings-item label="Anonymous join">
<aha-switch slot="control" checked></aha-switch>
</aha-settings-item>
</aha-setting-group>
<!-- A danger zone — tone="danger" drops it 48px below the previous group (the CTA carries the danger look). -->
<aha-setting-group tone="danger" label="Danger zone">
<aha-settings-item label="Reset all scores">
<aha-button slot="control" danger>Reset</aha-button>
</aha-settings-item>
</aha-setting-group>
</div>
<script>
// The parent toggle hides its dependent sub-group when off — visibility is the author's job.
const sub = document.getElementById('timerSub');
document.getElementById('timerToggle').addEventListener('change', (e) => {
sub.hidden = !e.detail.checked;
});
</script>
import '@ahaslides-product/design/icons'; // <aha-icon> — the header "?" help glyph + row controls
import '@ahaslides-product/design/aha-setting-group'; // registers <aha-setting-group>
import '@ahaslides-product/design/aha-sub-setting-group'; // registers <aha-sub-setting-group>
import '@ahaslides-product/design/aha-settings-list'; // <aha-settings-item> rows
import '@ahaslides-product/design/aha-switch';
import '@ahaslides-product/design/aha-input';
import { useState } from 'react';
// <aha-setting-group> bakes in the spacing scale: rows 16px apart, groups 32px apart automatically,
// tone="danger" 48px down — no dividers, no cards. Put guidance in `help` (the "?" tooltip), an
// action in the `action` slot, and dependent settings in a nested <aha-sub-setting-group> that you
// hide when the parent is off.
function DeckSettings() {
const [timerOn, setTimerOn] = useState(true);
return (
<div style={{ maxWidth: 380 }}>
<aha-setting-group label="Presentation" help="How the deck shows to the audience.">
<aha-settings-item label="Progress bar">
<aha-switch slot="control" checked="" />
</aha-settings-item>
<aha-settings-item label="Question timer">
<aha-switch slot="control"
{...(timerOn ? { checked: '' } : {})}
onChange={(e) => setTimerOn(e.detail.checked)} />
</aha-settings-item>
{/* dependent settings — hidden when the parent toggle is off */}
<aha-sub-setting-group {...(timerOn ? {} : { hidden: '' })}>
<aha-settings-item label="Seconds per question">
<aha-input slot="control" value="30" />
</aha-settings-item>
<aha-settings-item label="Auto-advance">
<aha-switch slot="control" />
</aha-settings-item>
</aha-sub-setting-group>
</aha-setting-group>
{/* Group 2 — no wiring: it sits 32px below automatically. */}
<aha-setting-group label="Audience">
<aha-settings-item label="Anonymous join">
<aha-switch slot="control" checked="" />
</aha-settings-item>
</aha-setting-group>
</div>
);
}
// main.ts — register the elements + mark aha-* as custom elements
import '@ahaslides-product/design/icons'; // <aha-icon> — the header "?" help glyph + row controls
import '@ahaslides-product/design/aha-setting-group'; // registers <aha-setting-group>
import '@ahaslides-product/design/aha-sub-setting-group'; // registers <aha-sub-setting-group>
import '@ahaslides-product/design/aha-settings-list'; // <aha-settings-item> rows
import '@ahaslides-product/design/aha-switch';
import '@ahaslides-product/design/aha-input';
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue — <aha-setting-group> bakes in the spacing scale (rows 16 · groups 32 · danger 48).
// Guidance goes in `help` (the "?" tooltip); dependent settings nest in <aha-sub-setting-group>,
// hidden when the parent is off.
<script setup>
import { ref } from 'vue';
const timerOn = ref(true);
</script>
<template>
<div style="max-width:380px">
<aha-setting-group label="Presentation" help="How the deck shows to the audience.">
<aha-settings-item label="Progress bar">
<aha-switch slot="control" checked />
</aha-settings-item>
<aha-settings-item label="Question timer">
<aha-switch slot="control" :checked.prop="timerOn"
@change="timerOn = $event.detail.checked" />
</aha-settings-item>
<!-- dependent settings — hidden when the parent toggle is off -->
<aha-sub-setting-group :hidden="!timerOn">
<aha-settings-item label="Seconds per question">
<aha-input slot="control" value="30" />
</aha-settings-item>
<aha-settings-item label="Auto-advance">
<aha-switch slot="control" />
</aha-settings-item>
</aha-sub-setting-group>
</aha-setting-group>
<!-- Group 2 — no wiring: it sits 32px below automatically. -->
<aha-setting-group label="Audience">
<aha-settings-item label="Anonymous join">
<aha-switch slot="control" checked />
</aha-settings-item>
</aha-setting-group>
</div>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | — | Optional group header (a 1–3 word noun phrase). When set, renders a semibold header and becomes the group's aria-label |
help | string | — | Optional guidance shown as the "?" help tooltip (composed DS <aha-tooltip help>) after the header label — never a standing line |
tone | 'default' | 'danger' | default | danger → a danger-zone group, dropped 48px below the previous group (overrides the automatic 32). Spacing only — the danger CTA styling lives on the button |
action | slot | — | Forwarded to the header, right-aligned — e.g. a small link or button beside the group title |
(default slot) | slot | — | The setting rows / sub-groups; they stack 16px apart automatically |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-setting-group'; // registers <aha-setting-group>
Agent feed for this component (absolute, fetchable anywhere): setting-group.agent.json · setting-group.md · setting-group.llms.txt
When to use
When to use
- Setting group — a titled cluster of related settings in a panel — it bakes in the 16/32/48 spacing scale
- Sub setting group — dependent child settings that appear under one parent toggle (indent + tight gap)
- Settings list — you want the whole surface rendered from a schema (rows + reused DS controls) in one element
Reach for a group whenever you place more than one setting in a panel — it removes the temptation to eyeball gaps or draw a divider. Title it with a noun phrase; put guidance in the "?" help, not a standing line. Never wrap plain settings in a card or separate them with a line — hierarchy is spacing. Reserve tone="danger" for a genuine danger zone (delete / reset), and put the destructive styling on the CTA button inside it.
Surfaces
editor settings dashboard
Spec
Rows column · 16px between sibling settings (the settings scale) · header→rows 12px · Between groups 32px automatically (:host(:not(:first-child))) — no divider, no wiring · Danger zone tone=danger → 48px above (overrides the 32); the danger CTA styling lives on the button, not here · Header optional (label) · 14/21 Semibold (600) #1A1A1A · optional "?" help (composed <aha-tooltip help>) · action slot pushed right (margin-left:auto) · Structure no divider lines, no card/box — spacing only, white background