Use it for
Reach for BbAlert when the message stays on the page: errors above a form, a
quota warning, a "saved" confirmation in the panel that produced it.
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
Communicate a page state
Start with the state the page needs the user to resolve. For a failed submit, keep one summary above the form and leave field-level errors on their controls.
<template>
<form class="flex max-w-md flex-col gap-3" @submit.prevent="submit">
<BbAlert
v-if="submitted && missing.length > 0"
hide-close
icon="lucide:circle-x"
title="The invitation was not sent"
variant="destructive"
>
<ul class="list-inside list-disc">
<li v-for="problem in missing" :key="problem">{{ problem }}</li>
</ul>
</BbAlert>
<BbTextInput
id="invite-name"
v-model="name"
label="Full name"
name="name"
/>
<BbTextInput
id="invite-email"
v-model="email"
label="Work email"
name="email"
type="email"
/>
<BbButton class="self-start" type="submit" variant="primary">
Send invitation
</BbButton>
</form>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { BbAlert, BbButton, BbTextInput } from 'bitboss-ui';
const name = ref('');
const email = ref('');
const submitted = ref(false);
const missing = computed(() => [
...(name.value.trim() ? [] : ['Enter the full name of the person to invite']),
...(email.value.includes('@') ? [] : ['Enter a valid work email address']),
]);
const submit = () => {
submitted.value = true;
};
</script>
Use hide-close when the message clears only after the underlying state is
fixed. When the message is only a string, pass it as text.
Variants
Pick the variant by intent, not decoration: outline for neutral information,
primary for a product note, warning for something the user should attend to,
destructive for a failure.
<template>
<div class="flex max-w-md flex-col gap-3">
<BbAlert
hide-close
icon="lucide:info"
text="The catalogue shows net prices; VAT is added at checkout."
title="Prices exclude VAT"
/>
<BbAlert
hide-close
icon="lucide:rocket"
text="Warehouse levels now refresh every 15 minutes."
title="Stock sync is faster"
variant="primary"
/>
<BbAlert
hide-close
icon="lucide:triangle-alert"
:text="`${keyboard.name} is down to ${keyboard.stock} units.`"
title="Low stock"
variant="warning"
/>
<BbAlert
hide-close
icon="lucide:circle-x"
text="Row 42 has no SKU, so nothing was imported."
title="Import failed"
variant="destructive"
/>
</div>
</template>
<script setup lang="ts">
import { BbAlert } from 'bitboss-ui';
import { productById } from '~/demo-data';
const keyboard = productById[113]!;
</script>
outline is the default. No variant brings an icon of its own, so choose icon
alongside the variant.
There is no success variant. Use primary for a positive confirmation, or
register your own.
Coming from v2theme → variant
In v2 theme took any string and painted nothing: v2 shipped no
.bb-alert--* rules at all. In v3 the string is live, and theme="warning"
becomes an amber alert, quietly. Grep your stylesheets for
.bb-alert--<value>: if nothing styled it, drop the prop.
Dismissal and v-model
The close button renders by default and works without a v-model. For a message
nobody should be able to close, use hide-close.
<template>
<div class="flex max-w-md flex-col gap-3">
<!--
No v-if here: the component owns its own visibility and animates
itself out when the model turns false.
-->
<BbAlert
v-model="reminderVisible"
close-label="Dismiss the email verification reminder"
icon="lucide:mail"
:text="`We sent a link to ${owner.email}. It expires in 24 hours.`"
title="Verify your email"
variant="primary"
/>
<BbButton
v-if="!reminderVisible"
class="self-start"
prepend:icon="lucide:undo-2"
variant="ghost"
@click="reminderVisible = true"
>
Bring the reminder back
</BbButton>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbAlert, BbButton } from 'bitboss-ui';
import { userById } from '~/demo-data';
const owner = userById[1]!;
// Bound, so the page can read the state back and restore it. Left unbound the
// close button would still work — it would just be a one-way trip.
const reminderVisible = ref(true);
</script>
Bind v-model to observe the state and to put the alert back: setting it to
true replays the same transition inward. The alert owns its own visibility, so
it needs no wrapping v-if.
Coming from v2show-close → hide-close
The polarity flipped: :show-close="false" becomes hide-close, and a
show-close left at its default is simply deleted. In development the library
warns when it still finds a v2 name on the component.
Rich content
The #title and #text slots replace the heading and the body. Both receive
{ text }, so you can decorate the string you were handed instead of restating
it.
<template>
<div class="max-w-md">
<BbAlert
hide-close
icon="lucide:sparkles"
text="Vantera Free keeps three active projects."
variant="primary"
>
<template #title> You are on the <strong>Free</strong> plan </template>
<!--
The slot receives the `text` prop, so the string stays where it is
and the slot only decorates it.
-->
<template #text="{ text }">
{{ text }}
<a class="font-medium underline" href="#pricing">Compare plans</a>
</template>
</BbAlert>
</div>
</template>
<script setup lang="ts">
import { BbAlert } from 'bitboss-ui';
</script>
Three sources can supply the body, and the most specific wins: #text, then
child content, then the text prop. There is no actions slot: a real anchor in
the text is fine, anything more interactive belongs outside the alert.
Coming from v2
The #text slot's scope used to hand you the title by mistake. It now carries
the text, so any workaround that read the prop from outside the slot can go.
Validation summaries
A destructive alert above the form, appearing on the first failed submit and
listing what is missing. Give it hide-close: the user clears this message by
fixing the fields.
A summary does not replace per-field errors, which stay on the inputs through
errors. Keep it to one: five alerts appearing together talk over each other.
How an alert is announced
warning and destructive announce as role="alert" and
aria-live="assertive". Every other variant uses role="status" and
aria-live="polite".
Force either one with the role and aria-live props when visual tone and
urgency come apart.
<BbAlert
aria-live="assertive"
role="alert"
icon="lucide:cloud-off"
title="Autosave failed"
text="Your last edits exist only on this device."
/>
Never let several assertive alerts appear at once: they cut each other off.
Coming from v2
The alert is always aria-atomic, so a changed list re-announces whole. The v2
prop that turned that off is gone.
Custom variants
Register a name in the plugin's alertVariants and it becomes a typed value of
variant.
// nuxt.config.ts
export default defineNuxtConfig({
bitboss: { alertVariants: ['success'] },
});
Registered variants ship no CSS. You paint .bb-alert--<name> yourself, setting
the same two custom properties the built-ins set: --main-color for the title
and the icon, --muted-color for the body text and the close button.
.bb-alert--success {
--main-color: #15803d;
--muted-color: color-mix(in oklab, var(--main-color) 75%, transparent);
background-color: transparent;
border-color: var(--bb-border);
}
A registered variant is polite however alarming its name. Pass role="alert"
and aria-live="assertive" on the instances that genuinely are urgent.