;
/**
* Default icon-only button. Requires `aria-label` for screen reader accessibility.
*/
export const IconButtonDefault: Story = {
args: {
"aria-label": "Close",
icon: ,
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: "Close" });
await step("IconButton is rendered with aria-label", async () => {
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute("aria-label", "Close");
});
await step("IconButton receives focus on tab", async () => {
await userEvent.tab();
expect(button).toHaveFocus();
});
await step("IconButton click handler fires", async () => {
await userEvent.click(button);
expect(iconClicked).toHaveBeenCalled();
});
},
};
/**
* Uses `aria-labelledby` instead of `aria-label` — references an existing element in the DOM.
* The XOR type means passing both `aria-label` and `aria-labelledby` is a TypeScript error.
*/
export const IconButtonLabelledBy: Story = {
render: () => (
Delete item
}
/>
),
};
/**
* Icon + visible label. Label hides below 768px (overridable via `$icon-label-bp` SCSS variable).
* Resize the viewport to see the responsive behavior.
* NOTE: `variant="outline"` overrides the default `variant="icon"` to restore padding.
*/
export const IconButtonWithLabel: Story = {
args: {
"aria-label": "Settings",
icon: ,
label: "Settings",
variant: "outline",
},
};
/**
* All style variants — icon (default), outline, text, and pill.
* `icon` is the default: transparent background, currentColor icon, square touch target.
* Switch `variant` to restore background or border as needed.
*/
export const IconButtonVariants: Story = {
render: () => (
} />
} variant="outline" />
} variant="text" />
} variant="pill" />
),
};
/**
* Size variants — xs through 2xl. Height and touch target scale with font size
* via the `--btn-height: calc(var(--btn-fs) * 2.75)` formula.
*/
export const IconButtonSizes: Story = {
render: () => (
} size="xs" />
} size="sm" />
} size="md" />
} size="lg" />
} size="xl" />
} size="2xl" />
),
};
/**
* All semantic color variants. Color sets `--btn-bg` and `--btn-color` via
* `data-color` — icon buttons keep a transparent background by default so the
* icon itself inherits the color token via `currentColor`.
*/
export const IconButtonColors: Story = {
render: () => (
} color="primary" />
} color="secondary" />
} color="danger" />
} color="success" />
} color="warning" />
),
};
/**
* Outline variant across all color tokens. The `outline` variant restores a border
* and uses `currentColor` for both border and icon — color sets the inherited value.
*/
export const IconButtonOutlineColors: Story = {
render: () => (
} variant="outline" color="primary" />
} variant="outline" color="secondary" />
} variant="outline" color="danger" />
} variant="outline" color="success" />
} variant="outline" color="warning" />
),
};
/**
* Disabled state — uses the WCAG-compliant `aria-disabled` pattern.
* The button remains focusable but all interactions are blocked.
*/
export const IconButtonDisabled: Story = {
args: {
"aria-label": "Close (disabled)",
icon: ,
disabled: true,
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: "Close (disabled)" });
await step("Disabled button has aria-disabled attribute", async () => {
expect(button).toHaveAttribute("aria-disabled", "true");
});
await step("Disabled button remains focusable", async () => {
await userEvent.tab();
expect(button).toHaveFocus();
});
},
};