Button
A clickable element that triggers an action.
Props#
| Name | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "ghost" | "danger" | primary | Visual style variant. |
size | "sm" | "md" | "lg" | md | Button size. |
disabled | boolean | false | Disable the button — non-interactive and visually muted. |
loading | boolean | false | Show a loading indicator instead of children. |
fullWidth | boolean | false | Stretch the button to fill its container's width. |
onClick | (() => void) | — | Click handler. |
children* | ReactNode | — | Button label. |
Interactive#
Show code
Interactive.stories.tsx
import { Button, type ButtonProps } from '../../../src/pixie/Button.js';
/**
* Interactive story: every prop in `args` becomes a control below the
* preview. Changing a control re-mounts the story with the new props.
*/
export const args: ButtonProps & { children: string } = {
variant: 'primary',
size: 'md',
disabled: false,
loading: false,
fullWidth: false,
children: 'Click me',
};
/**
* Optional explicit control types. If omitted, Markbook infers from the
* runtime arg value (boolean → checkbox, number → number input, else text).
*/
export const argTypes = {
variant: {
control: 'select' as const,
options: ['primary', 'secondary', 'ghost', 'danger'],
},
size: { control: 'select' as const, options: ['sm', 'md', 'lg'] },
disabled: { control: 'boolean' as const },
loading: { control: 'boolean' as const },
fullWidth: { control: 'boolean' as const },
children: { control: 'text' as const },
};
export const parameters = {
layout: 'centered' as const,
};
export default function Interactive(props: typeof args) {
return <Button {...props} />;
}Variants#
Show code
import { Button } from '../../../src/pixie/Button.js';
import styles from './Variants.module.css';
export default () => (
<div className={styles.row}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Delete</Button>
</div>
);/* CSS Module — class names are scoped/hashed by Vite. */
.row {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
padding: 0.75rem;
border: 1px dashed var(--mb-accent, #6c5ce7);
border-radius: 6px;
}Sizes#
Show code
import { Button } from '../../../src/pixie/Button.js';
import styles from './Sizes.module.css';
export default () => (
<div className={styles.row}>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
);.row {
display: flex;
gap: 0.5rem;
align-items: center;
}Loading#
Show code
Loading.stories.tsx
import { Button } from '../../../src/pixie/Button.js';
export default () => <Button loading>Saving</Button>;Disabled#
Show code
Disabled.stories.tsx
import { Button } from '../../../src/pixie/Button.js';
export default () => <Button disabled>Unavailable</Button>;Full width#
Show code
FullWidth.stories.tsx
import { Button } from '../../../src/pixie/Button.js';
export default () => (
<div style={{ width: 320 }}>
<Button fullWidth>Full width</Button>
</div>
);