Button
A button fires an action. It carries the press, hover and focus behaviour the rest of the library reuses, which is why a dropdown, a modal and a tooltip each wrap one to make a trigger.
import { Button } from "ropav";<Button>Get started</Button>Variants
button-variants.vue
<script setup lang="ts">
import { Button } from "ropav";
const variants = [
"primary",
"secondary",
"tertiary",
"outline",
"ghost",
"danger",
"danger-soft",
] as const;
</script>
<template>
<Button v-for="variant in variants" :key="variant" :variant="variant">
{{ variant }}
</Button>
</template>Sizes
The default size lives in the base class, so a button with no size never looks unstyled.
button-sizes.vue
<script setup lang="ts">
import { Button } from "ropav";
const sizes = ["xs", "sm", "md", "lg"] as const;
</script>
<template>
<Button v-for="size in sizes" :key="size" :size="size">{{ size }}</Button>
</template>Icons
An icon goes in the default slot beside the label. A button whose whole content is one icon takes is-icon-only, and needs an aria-label — there is no text left to name it.
button-icons.vue
<script setup lang="ts">
import { Button, IconPlus, IconSearch } from "ropav";
</script>
<template>
<Button>
<IconPlus />
New item
</Button>
<Button variant="secondary">
Search
<IconSearch />
</Button>
<Button aria-label="Search" is-icon-only variant="ghost">
<IconSearch />
</Button>
</template>Pending and disabled
is-pending marks an in-flight action. It renders data-pending and blocks activation while keeping the button focusable, so assistive technology can still reach it and hear what changed. is-disabled is the other choice on purpose: a disabled button leaves the tab order entirely.
A pending button that would otherwise submit a form is switched to type="button" while it waits. Blocking the click is not enough on its own — implicit submission reaches the form through the button's type, without a click ever landing.
button-pending.vue
<script setup lang="ts">
import { Button } from "ropav";
import { ref } from "vue";
const isPending = ref(false);
const save = () => {
isPending.value = true;
setTimeout(() => (isPending.value = false), 2000);
};
</script>
<template>
<Button :is-pending="isPending" @click="save">Save changes</Button>
<Button is-disabled variant="secondary">Disabled</Button>
</template>Full width
button-full-width.vue
<script setup lang="ts">
import { Button } from "ropav";
</script>
<template>
<div class="stack">
<Button full-width>Continue</Button>
<Button full-width variant="secondary">Cancel</Button>
</div>
</template>
<style scoped>
.stack {
display: flex;
width: 100%;
max-width: var(--rp-container-sm);
flex-direction: column;
gap: calc(var(--rp-spacing) * 3);
}
</style>Styling
Every component takes a class prop, appended to the classes its recipe already carries. Component rules live in the components layer and utilities in the later utilities one, so a utility passed this way wins on layer order alone — nothing has to be stripped for it to land.
A colour that more than one state paints goes through a custom property instead, so a single state can be retuned without flattening the rest. Those are set with style, which needs no build of any kind.
<!-- `w-full` is a class of yours; the layer order is what makes it land -->
<Button class="w-full" variant="secondary">Save</Button>
<Button style="--button-bg-hover: var(--rp-success)">Publish</Button>Accessibility
- Renders a native
<button>, andtypedefaults to"button"so it never submits by accident. - An explicit
tabindexis written even though a native button is already tabbable: Safari does not focus one without it. A disabled button gets none, so it is not reachable at all. - Moving into and out of the pending state is announced, but only while the button is focused — that is when the change is part of what the reader is doing.
- Under Forced Colors Mode the focus ring is redrawn as an outline, because the mode strips
box-shadowand every ring in this library is one.
API
| Prop | Type | Default | Description |
|---|---|---|---|
fullWidth | boolean | — | Stretches the button to the full width of its container. |
isDisabled | boolean | — | Disables the button. |
isIconOnly | boolean | — | Renders the icon-only shape, for a button whose content is a single icon. |
isPending | boolean | — | Marks an in-flight action. Renders data-pending and blocks activation, while keeping the button focusable so assistive technology can still reach it. |
size | "xs" | "sm" | "md" | "lg" | "md" | Button size. |
type | "button" | "submit" | "reset" | "button" | Native button type. |
variant | "primary" | "secondary" | "tertiary" | "outline" | "ghost" | "danger" | "danger-soft" | "primary" | Button variant. |
The default slot receives:
| Slot prop | Type |
|---|---|
isDisabled | boolean |
isFocusVisible | boolean |
isHovered | boolean |
isPending | boolean |
isPressed | boolean |
| Event | Payload |
|---|---|
click | MouseEvent |