{
  "generatedFrom": "settings-list.contract.json",
  "component": "Settings list",
  "slug": "settings-list",
  "group": "Settings",
  "tier": "leaf-lit",
  "summary": "A schema-driven group of settings rows — semibold label left (with a \"?\" help tooltip), control right — reusing existing DS controls.",
  "install": {
    "package": "@ahaslides-product/design",
    "registry": "https://npm.pkg.github.com",
    "scope": "@ahaslides-product",
    "auth": "Published to GitHub Packages — needs a GitHub token with read:packages. Configure the @ahaslides-product scope in .npmrc before installing.",
    "npmrc": "@ahaslides-product:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}   # a GitHub token with read:packages",
    "command": "npm i @ahaslides-product/design",
    "tokenLayer": "import '@ahaslides-product/design/tokens.css';",
    "import": "import '@ahaslides-product/design/aha-settings-list';",
    "element": "aha-settings-list",
    "registers": "aha-settings-list"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/settings-list/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/settings-list/settings-list.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/settings-list.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/settings-list.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-settings-list> is a standard custom element that renders on open — React/Vue are thin adapters over the same element. Paste into an .html file and it renders with no build step.",
  "frameworks": {
    "react": {
      "via": "web-component",
      "ref": "aha-settings-list"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-settings-list"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-settings-list",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "schema",
      "type": "object",
      "default": "—",
      "desc": "(list, property) Settings schema { sections:[{ label, rows:[{ key, label, description?, layout?, consequence?, control }] }] } — an array is treated as one section's rows; renders rows and reuses DS controls. Also settable as a JSON `schema` attribute for the build-free HTML form A row may declare visible_if (structured `visibleIf:{key,equals?,in?,not?}` or the Shopify string `visible_if:\"{{ settings.<key> }}\"`) — the row is shown only when its trigger is on and hidden when off, rendered nested (indented, de-emphasised)."
    },
    {
      "name": "label",
      "type": "string",
      "default": "—",
      "desc": "Group header (a 1–3 word noun phrase); on <aha-settings-item> it is the setting name. Ignored when a schema supplies section labels"
    },
    {
      "name": "description",
      "type": "string",
      "default": "—",
      "desc": "(item / schema row) Guidance text — rendered as the \"?\" HELP TOOLTIP after the label (DS <aha-tooltip help>), NOT a standing line. Default: no help glyph"
    },
    {
      "name": "consequence",
      "type": "string",
      "default": "—",
      "desc": "(item / schema row) RARE opt-in must-see-consequence line shown as a standing line under the label (e.g. 'This cannot be undone.') — default none"
    },
    {
      "name": "layout",
      "type": "'inline' | 'stack'",
      "default": "inline",
      "desc": "(item / schema row) inline = label left / control right (align-items:center, space-between, gap 12); stack = label above / control full-width (column, gap 8) for wide controls (textarea / wide select)"
    },
    {
      "name": "control",
      "type": "slot | schema",
      "default": "—",
      "desc": "(item) The DS control on the right — <aha-switch>, <aha-checkbox>, <aha-input>… slotted by hand, or named in a schema row's `control:{ type }` and instantiated for you"
    },
    {
      "name": "density",
      "type": "'default' | 'compact'",
      "default": "default",
      "desc": "(list) Row/group rhythm. compact tightens the spacing (16 between groups, 8 between siblings) — never adds lines or boxes"
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "desc": "(item / schema row) Dims the label to text-disabled, disables the reused control, sets aria-disabled"
    },
    {
      "name": "locked",
      "type": "boolean",
      "default": "false",
      "desc": "(schema row) Plan-gates the row: stays visible, its control becomes the shared Paywall crown badge → upsell. Pair with `plan` ('essential'|'pro') and `feature` (the upsell label)"
    }
  ],
  "tokens": [
    "text-default",
    "text-tertiary",
    "text-disabled",
    "color-primary"
  ],
  "spec": [
    {
      "label": "Group",
      "value": "column · 16px between sibling settings · 32px between groups · semibold (600) header (SectionHeader); member/row labels are ALSO semibold (600) per the reference SettingRow"
    },
    {
      "label": "Row (inline, default)",
      "value": "label left, control right · align-items:center · justify-content:space-between · min-height 24 · 12px gap"
    },
    {
      "label": "Row (stack)",
      "value": "layout=stack → label above, control full-width (flex-direction:column · 8px gap) — for wide controls (textarea / wide select)"
    },
    {
      "label": "Label",
      "value": "14/21 Semibold (600) #1A1A1A (name = noun phrase, no leading verb)"
    },
    {
      "label": "Help",
      "value": "guidance = a \"?\" help trigger after the label (DS <aha-tooltip help>, icon system-question-mark, muted ~50% = text-tertiary #8A8A8A → brand on hover/focus) + tooltip; NO standing description line. `description` is the tooltip content"
    },
    {
      "label": "Consequence (rare)",
      "value": "opt-in `consequence` → a standing must-see line 12/18 #8A8A8A under the label; default none"
    },
    {
      "label": "Disabled row",
      "value": "row disabled → label dims to text-disabled #B5B5B5, control disabled, aria-disabled"
    },
    {
      "label": "Plan-gated row",
      "value": "row locked → visible but the control is the shared Paywall crown (opens the upsell), never hidden"
    },
    {
      "label": "Density",
      "value": "density=compact tightens the rhythm (16 between groups, 8 between siblings) — spacing, never lines"
    },
    {
      "label": "Structure",
      "value": "no divider lines, no card/box around plain settings"
    },
    {
      "label": "Schema",
      "value": "sections[] → rows[] { key, label, description?, layout?, consequence?, disabled?, locked?, plan?, feature?, control:{ type, …props } }; renders + reuses DS controls"
    },
    {
      "label": "visible_if",
      "value": "a follow-up row shown only when its trigger is on (Shopify model) — nested (24 indent, tighter gap, de-emphasised label); hidden = display:none, no phantom gap (SETTINGS-19/07/14)"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Settings list",
        "when": "a group of related on/off or choice settings in a panel, page or drawer"
      },
      {
        "what": "Form",
        "when": "values are submitted together with validation and a submit step"
      },
      {
        "what": "a single Switch/Select",
        "when": "one setting that applies on its own"
      }
    ],
    "note": "Name each setting as a noun phrase (no leading verb — the control says on/off). Use a toggle for immediate effect, a checkbox for consent/saved-together — never mix in one group. Guidance lives in the \"?\" help tooltip after the label — never a standing description line (reserve the rare `consequence` line for a genuine must-see, e.g. an irreversible action). Hierarchy is spacing, never lines or boxes. Use `layout:'stack'` for wide controls (textarea / wide select). See the settings pattern for grouping, sub-settings and the danger zone.",
    "dsv3": "COVERED — this is the reusable component form of the settings pattern (parts/settings.guide.md): semibold (600) label / control rows, spacing-only grouping (16 between siblings, 32 between groups), no dividers or boxes. Faithful to SettingRow: inline (label left / control right, gap 12) or stack (label above / control full-width) layout, and a single \"?\" help tooltip (system-question-mark, muted) for guidance rather than a standing description line. It composes existing DS controls via a slot or a schema, and carries the pattern's full row matrix: plain, with-help, stack, disabled (dimmed to text-disabled), and plan-gated (visible-but-locked → the shared Paywall crown, never hidden). A deliberate, documented AhaSlides surface."
  },
  "surfaces": [
    "editor",
    "settings",
    "dashboard"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-settings-list> is schema-driven — pass a schema (here as the JSON `schema` attribute)\n     and it renders each row, REUSING existing DS controls (<aha-switch>, <aha-input>…) — it ships\n     no control of its own. The by-hand slot form is shown second. Theming comes only from the\n     --aha-* tokens in tokens.css. -->\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css\">\n<script type=\"module\">\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-settings-list.js';   // registers <aha-settings-list> + <aha-settings-item>\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-switch.js';          // reused as a schema control\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-input.js';           // reused as a schema control\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js';               // <aha-icon> — the select's caret + the crown\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-select.js';          // reused as a schema control (control.type \"select\")\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-paywall.js';         // reused for a plan-gated (locked) row\n</script>\n\n<!-- 1) Schema-driven — the list renders the rows and reuses the named DS controls.\n     `description` becomes a \"?\" help tooltip after the label (not a standing line); `layout:\"stack\"`\n     puts the label above a full-width control. A row can be disabled (dimmed) or locked\n     (visible-but-gated → the shared Paywall crown). -->\n<aha-settings-list style=\"max-width:360px; display:block\"\n  schema='{\"sections\":[{\"label\":\"Presentation\",\"rows\":[\n    {\"key\":\"progressBar\",\"label\":\"Progress bar\",\"control\":{\"type\":\"switch\",\"checked\":true}},\n    {\"key\":\"leaderboard\",\"label\":\"Leaderboard\",\"description\":\"Shown to the audience between slides.\",\"control\":{\"type\":\"switch\"}},\n    {\"key\":\"deckTitle\",\"label\":\"Deck title\",\"control\":{\"type\":\"input\",\"value\":\"Untitled\"}},\n    {\"key\":\"notes\",\"label\":\"Speaker notes\",\"description\":\"Only you see these while presenting.\",\"layout\":\"stack\",\"control\":{\"type\":\"input\",\"value\":\"\"}},\n    {\"key\":\"theme\",\"label\":\"Slide theme\",\"control\":{\"type\":\"select\",\"value\":\"light\",\"options\":[{\"label\":\"Light\",\"value\":\"light\"},{\"label\":\"Dark\",\"value\":\"dark\"},{\"label\":\"High contrast\",\"value\":\"contrast\"}]}},\n    {\"key\":\"timer\",\"label\":\"Question timer\",\"disabled\":true,\"control\":{\"type\":\"switch\"}},\n    {\"key\":\"branding\",\"label\":\"Custom branding\",\"locked\":true,\"plan\":\"pro\",\"feature\":\"Custom branding\"}\n  ]}]}'></aha-settings-list>\n<script type=\"module\">\n  document.querySelector('aha-settings-list')\n    .addEventListener('change', e => console.log('setting changed', e.detail)); // { key, value, name }\n</script>\n\n<!-- 2) Slot form — compose the controls by hand (still supported) -->\n<aha-settings-list label=\"Audience\" style=\"max-width:360px; display:block; margin-top:24px\">\n  <aha-settings-item label=\"Anonymous join\">\n    <aha-switch slot=\"control\" checked></aha-switch>\n  </aha-settings-item>\n</aha-settings-list>\n",
    "react": "import { useEffect, useRef } from 'react';\nimport '@ahaslides-product/design/aha-settings-list';   // registers <aha-settings-list> + <aha-settings-item>\nimport '@ahaslides-product/design/aha-switch';          // reused as schema controls\nimport '@ahaslides-product/design/aha-input';\nimport '@ahaslides-product/design/aha-paywall';         // reused for a plan-gated (locked) row\n\n// Schema-driven — set the schema property (an object) and listen for the change event.\n// `description` renders as a \"?\" help tooltip after the label (not a standing line);\n// `layout: 'stack'` puts the label above a full-width control (wide inputs). A row can be\n// disabled (dimmed) or locked (visible-but-gated → the shared Paywall crown).\nfunction DeckSettings() {\n  const ref = useRef(null);\n  useEffect(() => {\n    const el = ref.current;\n    el.schema = {\n      sections: [{ label: 'Presentation', rows: [\n        { key: 'progressBar', label: 'Progress bar', control: { type: 'switch', checked: true } },\n        { key: 'leaderboard', label: 'Leaderboard', description: 'Shown between slides.', control: { type: 'switch' } },\n        { key: 'deckTitle', label: 'Deck title', control: { type: 'input', value: 'Untitled' } },\n        { key: 'notes', label: 'Speaker notes', layout: 'stack', description: 'Only you see these.', control: { type: 'input' } },\n        { key: 'timer', label: 'Question timer', disabled: true, control: { type: 'switch' } },\n        { key: 'branding', label: 'Custom branding', locked: true, plan: 'pro', feature: 'Custom branding' },\n      ] }],\n    };\n    const onChange = (e) => console.log(e.detail); // { key, value, name }\n    el.addEventListener('change', onChange);\n    return () => el.removeEventListener('change', onChange);\n  }, []);\n  return <aha-settings-list ref={ref} />;\n}\n\n// Or compose by hand with the slot form — name = noun phrase (no leading verb).\n<aha-settings-list label=\"Audience\">\n  <aha-settings-item label=\"Anonymous join\">\n    <aha-switch slot=\"control\" checked />\n  </aha-settings-item>\n</aha-settings-list>\n",
    "vue": "// main.ts — register the elements + mark aha-* as custom elements\nimport '@ahaslides-product/design/aha-settings-list';   // registers <aha-settings-list> + <aha-settings-item>\nimport '@ahaslides-product/design/aha-switch';          // reused as schema controls\nimport '@ahaslides-product/design/aha-input';\nimport '@ahaslides-product/design/aha-paywall';         // reused for a plan-gated (locked) row\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue — schema-driven: bind the schema property, listen for change.\n// `description` renders as a \"?\" help tooltip after the label (not a standing line);\n// `layout: 'stack'` puts the label above a full-width control. A row can be disabled\n// (dimmed) or locked (visible-but-gated → the shared Paywall crown).\n<script setup>\nimport { ref, onMounted } from 'vue';\nconst list = ref(null);\nconst schema = {\n  sections: [{ label: 'Presentation', rows: [\n    { key: 'progressBar', label: 'Progress bar', control: { type: 'switch', checked: true } },\n    { key: 'leaderboard', label: 'Leaderboard', description: 'Shown between slides.', control: { type: 'switch' } },\n    { key: 'deckTitle', label: 'Deck title', control: { type: 'input', value: 'Untitled' } },\n    { key: 'notes', label: 'Speaker notes', layout: 'stack', description: 'Only you see these.', control: { type: 'input' } },\n    { key: 'timer', label: 'Question timer', disabled: true, control: { type: 'switch' } },\n    { key: 'branding', label: 'Custom branding', locked: true, plan: 'pro', feature: 'Custom branding' },\n  ] }],\n};\nonMounted(() => { list.value.schema = schema; });\n</script>\n\n<template>\n  <!-- schema-driven -->\n  <aha-settings-list ref=\"list\" @change=\"e => console.log(e.detail)\" />\n\n  <!-- or the by-hand slot form -->\n  <aha-settings-list label=\"Audience\">\n    <aha-settings-item label=\"Anonymous join\">\n      <aha-switch slot=\"control\" checked />\n    </aha-settings-item>\n  </aha-settings-list>\n</template>\n"
  }
}
