Patterns · Settings
Add Item Button
The full-width "+ Add" affordance that sits under a settings list (OptionRow / QuestionList) — a dashed, tertiary add row that appends a new item.
Examples
Label
Disabled
Add row · under a settings list · full width
States · rest · disabled (at max count)
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-add-item-button> is the SAME shared custom element React and Vue consume — here in its
native form. Theming comes only from the --aha-* tokens in tokens.css.
The + glyph is summoned by name from the DS icon library via <aha-icon>. -->
<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/aha-add-item-button.js'; // registers <aha-add-item-button> (also pulls in <aha-icon>)
</script>
<!-- full-width add row — sits under an editable settings list; disabled = at the max item count -->
<div style="width:320px">
<aha-add-item-button id="add" label="Add option"></aha-add-item-button>
<br>
<aha-add-item-button label="Add option" disabled></aha-add-item-button>
</div>
<script>
// Plain DOM — no framework. The composed `add` CustomEvent fires on click; append a new item here.
let count = 1;
document.getElementById('add').addEventListener('add', () => {
console.log('add item #', ++count);
});
</script>
import '@ahaslides-product/design/aha-add-item-button'; // registers <aha-add-item-button> (also pulls in <aha-icon>)
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper for the composed `add` event; React 19 can use <aha-add-item-button> directly.
function AhaAddItemButton({ label = 'Add', disabled, onAdd }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
const h = () => onAdd?.(); // composed CustomEvent, no payload
el.addEventListener('add', h);
return () => el.removeEventListener('add', h);
});
return (
<aha-add-item-button
ref={ref}
label={label}
{...(disabled ? { disabled: '' } : {})}
/>
);
}
// usage
function OptionList({ options, setOptions, max = 10 }) {
return (
<>
{/* …option rows… */}
<AhaAddItemButton
label="Add option"
disabled={options.length >= max}
onAdd={() => setOptions([...options, { text: '' }])}
/>
</>
);
}
// main.ts — register the element + mark aha-* as custom elements
import '@ahaslides-product/design/aha-add-item-button'; // registers <aha-add-item-button> (also pulls in <aha-icon>)
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref, computed } from 'vue';
const options = ref([{ text: '' }]);
const max = 10;
const atMax = computed(() => options.value.length >= max);
</script>
<template>
<!-- full-width add row under the option list; disabled at the max item count -->
<!-- …option rows… -->
<aha-add-item-button
label="Add option"
:disabled.prop="atMax"
@add="options.push({ text: '' })"
/>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | Add | The add-affordance text; carries the accessible name |
disabled | boolean | false | Non-interactive (e.g. at the max item count); muted, no hover, sets aria-disabled |
add | CustomEvent<{}> | — | Composed event fired on click when enabled — the host appends a new item |
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-add-item-button'; // registers <aha-add-item-button>
Agent feed for this component (absolute, fetchable anywhere): add-item-button.agent.json · add-item-button.md · add-item-button.llms.txt
When to use
When to use
- Add Item Button — append a new row to an editable settings list — options, questions, list items
- Button (tertiary) — a standalone action that isn't 'add another row to this list'
Place it directly under the list it grows, full-width so it reads as part of the stack. Label it with the item noun — 'Add option', 'Add question' — not a bare 'Add' when the context needs it. Disable (don't hide) it at the max count so the ceiling stays discoverable; pair with a tooltip or helper line explaining the limit.
Surfaces
editor settings dashboard
Spec
Box full width (100%) · height 40 · radius 8 · padding 0 12 · Border 1px dashed #E3E3E3 (rest) · Content centered — system-plus 16px + label, 8px gap · Text Plus Jakarta 14/600, #4A4A4A (secondary) · Hover border + text → #6A1EBB, #F9F5FF fill (animated) · Focus #6A1EBB border + 2px soft ring · Disabled #B5B5B5 text, #EBEBEB border, not-allowed, no hover, aria-disabled