# Buttons Use the button components in `packages/ui/src/components/base/buttons/` for actions, navigation, file selection, and button-owned menus. They share the same sizing, interaction states, focus treatment, and visual variants. Do not recreate button styling with raw ` ``` Use `ButtonLink` for navigation. Pass exactly one of `to` or `href`: ```vue Settings Documentation ``` `to` creates a Vue Router link. `href` creates a standard anchor. External links opened in a new tab receive `rel="noopener noreferrer"` unless a different `rel` is provided. Use `IconButton` when the visible icon is the entire button content. The `label` prop is required and supplies the button's accessible name: ```vue ``` Do not replace `label` with a tooltip or rely on the icon to communicate the action. User-visible labels, including `IconButton` labels and menu option labels, must use the localization system. ## Visual types All button components use the same four visual types: | Type | Use | | ---------- | ------------------------------------------------------------------- | | `base` | Default neutral actions | | `colored` | The highest-emphasis action in a section | | `outlined` | Secondary actions that need a visible boundary | | `quiet` | Low-emphasis actions in toolbars, cards, and other compact contexts | `base` is the default. Use `colored` deliberately: a group should normally have only one highest-emphasis action. The available colors are `brand`, `red`, `orange`, `green`, `blue`, `purple`, and `medal_promotion`. Color is supported by `colored`, `outlined`, and `quiet`; `base` uses its standard neutral treatment. An outlined button without a color uses the standard neutral treatment. ```vue ``` Quiet buttons support an `interaction` prop when their hover and keyboard-focus treatment needs to differ from the default surface fill: | Interaction | Treatment | | ----------- | --------- | | `surface` | Uses the standard neutral hover/focus surface. This is the default. | | `filled` | Fills with the button's `color` and uses contrast text. | | `none` | Keeps the background transparent while retaining the focus ring. | ```vue ``` Use `interaction` to describe behavior rather than passing arbitrary hover colors. Resting selected-state backgrounds, such as a current pagination page, remain the responsibility of the owning component. Use red only for destructive or dangerous actions. Do not use color as the only way to communicate an action's meaning. ## Sizes - `sm`: Notifications and popups, usually for icon-only actions. - `md`: Modal and card actions that do not appear at the top of a page. - `lg`: Actions at the top of a page or subpage, such as beside inputs or filters. - `xl`: Header actions. `md` is the default. Buttons that appear together must use the same size. Prefer the size prop over overriding height, padding, border radius, font weight, gap, or icon dimensions with utility classes. Width and layout utilities such as `w-full` are fine when required by the surrounding layout. ```vue ``` ## States Use `disabled` when an action is unavailable. Use `loading` while a `Button` or `IconButton` action is in progress: ```vue ``` `loading` disables the underlying button and sets `aria-busy`. Keep the label stable so the action remains understandable while it is running. Disabled links are made non-navigable and removed from the tab order by `ButtonLink`. Do not reimplement disabled-link click handling in consumers. ## Icons and content Place icons directly inside the component so the shared styles can size and space them: ```vue ``` Icons next to a visible label are decorative and should use `aria-hidden="true"`. Use `IconButton` instead of an empty text button for icon-only actions. Button labels should describe the action directly. Avoid vague labels such as "Yes", "OK", or "Submit" when a specific label such as "Delete version" or "Save settings" would be clearer. ## File buttons `FileButton` owns the file input and emits validated files: ```vue ``` The `change` event receives a `File[]`. `allow-drop` defaults to `true`; set it to `false` when the surrounding interface should not accept dropped files. Use `FileButton` for a compact file-selection action. Use a dedicated drop area when drag-and-drop is the primary interaction or the interface needs previews, progress, or more detailed instructions. ## Button groups and split buttons Use `ButtonGroup` only for closely related controls. Pass a localized `label` when the relationship is not already clear from surrounding accessible content: ```vue ``` `ButtonGroup` joins the buttons visually, so its direct children must be components from this button system. Use `SplitButton` when one action is primary and closely related alternatives belong in an attached menu: ```vue ``` Use `primary-disabled` and `menu-disabled` when only one side is unavailable. Use `disabled` when the entire split button is unavailable. ## Overflow menus `TeleportOverflowMenu` is for a menu of discrete actions and navigation targets. Define its options as `OverflowMenuOption[]`: ```ts const options: OverflowMenuOption[] = [ { id: 'download', label: 'Download', icon: DownloadIcon, action: download, }, { id: 'project-page', label: 'Open project page', icon: ExternalIcon, type: 'link', to: projectRoute, }, { type: 'divider' }, { id: 'delete', label: 'Delete', icon: TrashIcon, tone: 'red', action: remove, }, ] ``` Each non-divider option needs a stable, unique `id` and a localized `label`. The trigger is an `IconButton` by default. Set `icon-only="false"` when the trigger has visible text or composite content, such as an avatar and chevron; it will use a normal `Button` so intrinsic width, padding, and gaps are preserved. - Use an action option for behaviour and a link option for navigation. - Use `shown: false` to remove an option conditionally. - Use `disabled` with a `tooltip` when the user needs to understand why an option is unavailable. - Use `remainOpen` only when selecting the option should not dismiss the menu. - Use `tone` for semantically colored options. The available tones are `brand`, `red`, `orange`, `green`, `blue`, `purple`, and `medal_promotion`. - Use `hoverFilled: true` when a toned option should fill with its tone on hover and focus. Use `hoverFilledOnly: true` to keep the default text color until that state. - Use `tone: 'red'` for destructive options. - Use dividers sparingly to separate meaningful groups. The component implements menu keyboard navigation, focus management, typeahead, and Escape handling. Consumers should not add competing keyboard or focus behaviour. Set `hoverable` when a navigation menu should also open on pointer hover. Click, touch, and keyboard activation remain available. ## Popout menus Use `TeleportPopoutMenu` for arbitrary interactive content that belongs to a button, such as a compact settings panel. Use `TeleportOverflowMenu` instead when the content is only a list of actions or links. ```vue ``` Set `icon-only` when the trigger has no visible text; `label` then becomes the trigger's accessible name. The panel role defaults to `dialog`; use `panel-role="region"` only when the content is non-modal supplementary content. The panel slot receives `close`, and the component manages initial focus, Escape, and focus restoration. ## Legacy components Do not introduce new uses of the legacy `packages/ui/src/components/base/Button.vue` or `ButtonStyled.vue` APIs. When migrating: | Legacy pattern | Replacement | | ------------------------------- | -------------------------------------------- | | `link` / `external` | `ButtonLink` with `to`, `href`, and `target` | | `action` | `@click` on `Button` | | `icon-only` | `IconButton` with `label` | | `large` | `size="lg"` or `size="xl"` | | `outline` | `type="outlined"` | | `transparent` | `type="quiet"` | | `ButtonStyled` wrapping an element | The matching direct button component | Preserve the original element's semantics while migrating. A visual match is not enough if a link becomes a button or an icon-only action loses its accessible name.