Primitive · 1 of 13
Seven variants off one prop. Four of them are pills; three are bare text links that share no box at all. Knowing which is which is the whole API.
Taken verbatim from defineProps<{...}>. A
* marks a required prop.
| Prop | Type | Notes |
|---|---|---|
| variant | 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'text' | 'link' | 'icon' | Falls through to primary when omitted — there is no explicit 'primary' case in the switch. |
| as | 'link' | Chooses the element: <a href> when 'link', else <button>. |
| title | string | The label. Optional — icon buttons omit it and let the slot carry the content. |
| href | string | Only read when as="link". |
| isDisabled | boolean | Visual only — see the warning below. |
| type | 'submit' | Declared in props but never bound in the template. |
| class | string | Appended to the variant class. This is how callers give primary its box. |
The four pill variants. primary is shown at a caller-supplied
width because on its own it has neither height nor padding.
These three use linkBaseClass, not the pill base. They have no
radius, no background, no height — only color and (for ghost) padding.
Swapping a primary for a text changes the layout,
not just the skin.
Hover and active are real here — move the pointer over the first canvas.
So is "disabled": these buttons carry isDisabled and you can
still click and focus them, because the prop is visual only (see findings).
isDisabled does not disable the button.
It only sets cursor-not-allowed opacity-20 on the class string.
The template never binds the disabled attribute, so the element
stays focusable, keyboard-activatable and clickable — and when
as="link" the <a> still navigates. Anything
relying on this prop to block submission is not protected.
The type prop is declared but never used.
type?: 'submit' appears in defineProps and then
nowhere in the template, so the rendered element keeps the implicit
type="submit" of a bare <button> inside a
form and type="button" semantics are unreachable. Passing
type has no effect either way.
primary has no box of its own.
Unlike secondary and tertiary it omits
h-11 px-6 py-1, so an unstyled <Button />
renders as a text-height pill. Every caller compensates through
class — footer.astro passes w-40,
Cookies.vue passes w-full max-w-[12.5rem]!. The
default variant is effectively unusable without a caller override.
Tertiary hover reduces contrast. The border moves #5F20FE → #4004AF (primary-700) on hover — darker, not lighter. Against the site's #0A0A0B ground that is roughly a 1.8:1 border, so the control dims at the moment of interaction. Faithfully reproduced above. (DESIGN.md §10.1 recorded this with the pre-redesign values — the direction is unchanged.)
variant="icon" is styling only.
There is no icon element branch — the button always renders
{{ title }} followed by the slot. An icon button is
variant="icon" with no title and the SVG in the
slot; nothing stops a caller passing both and getting a label crammed into
the p-5 square. (An earlier revision accepted as="icon" — the
current source only accepts as="link".)