Use it for
Use BbAsterisk for the small red * next to a required field. It has no
behaviour props, slots or events: it does not read required and it does not
validate anything. It draws the asterisk and exposes only root passthrough.
Use something else when
BbForm, if the question is how required fields get marked across a whole form
Pass Through
Hover or tap a part to outline it. Toggles flip loading, errors and warnings when the component has them — only parts highlight.
Most projects never place it by hand. The bitboss-ui/validated controls add it
once the plugin option is on. You need this component yourself only when you
replaced a control's label and the marker has to come back.
The marker
The markup is one span:
<span aria-hidden="true" class="bb-asterisk">*</span>
The marker is aria-hidden. What a screen reader announces as required is the required prop above it, not the glyph.
<template>
<div class="flex max-w-sm flex-col gap-3">
<BbTextInput
id="asterisk-workspace"
v-model="workspace"
label="Workspace name"
name="workspace"
required
>
<!-- The label slot replaces the text, so the marker goes back by hand. -->
<template #label="{ text }"> {{ text }}<BbAsterisk /> </template>
</BbTextInput>
<p class="text-sm text-[color:var(--bb-text-muted)]">
The marker is <code>aria-hidden</code>. What a screen reader announces
as required is the <code>required</code> prop above it, not the glyph.
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbAsterisk, BbTextInput } from 'bitboss-ui';
const workspace = ref('Northwind Studio');
</script>
aria-hidden is intentional: sighted users see the asterisk, screen readers do
not. A lone * read aloud in a label is noise. Required-ness for assistive tech
comes from required on the control.
Neither replaces the other. BbAsterisk does not make a field required.
required does not draw an asterisk until something places one.
Where it appears on its own
With requiredAsterisk on, every bitboss-ui/validated control puts the marker
in its #label or #legend slot:
// nuxt.config.ts
export default defineNuxtConfig({
bitboss: { requiredAsterisk: true },
});
// vite.config.ts, for a plain Vue app
bitbossUi({ requiredAsterisk: true });
It is off by default. That is a project-wide choice, not a per-field one: some required fields marked and others not is worse than no markers.
The marker shows when the field is required, or when its rules contain a
literal required rule. Both shapes count: the string form ('required|email',
including 'required:true') and the object form ({ required: true };
{ required: false } shows nothing).
Nothing else counts. Rules like max:5 pass on an empty field, so an asterisk
for "any rule" would mark optional fields. required_if and relatives do not
count either: conditionally required is not required.
Two cases stay out of reach: a validator function and a typed schema (zod, yup)
are opaque to the check. On a schema-driven field set required yourself when it
should carry the marker. Controls from the core bitboss-ui entrypoint ignore
the option: they have no rules to read, so place the marker yourself if you want
one.
In your own label
A control's label slot replaces the label text and anything the control was
drawing beside it. If the field is required, put the marker back.
<template>
<div class="flex max-w-sm flex-col gap-4">
<!--
A bespoke label: the field name, the marker, and a unit note that has
to sit on the same line rather than in `description`.
-->
<BbNumberInput
id="custom-label-quota"
v-model="quota"
label="Storage quota"
:max-precision="0"
:min="1"
name="quota"
required
>
<template #label="{ text, hasErrors }">
<span :class="hasErrors ? 'font-semibold' : undefined">{{ text }}</span>
<BbAsterisk />
<span class="ms-2 text-xs text-[color:var(--bb-text-muted)]">
per member
</span>
</template>
<template #suffix>GB</template>
</BbNumberInput>
<!-- No marker here: the field is genuinely optional. -->
<BbNumberInput
id="custom-label-overage"
v-model="overage"
label="Overage allowance"
:max-precision="0"
:min="0"
name="overage"
>
<template #label="{ text }">
<span>{{ text }}</span>
<span class="ms-2 text-xs text-[color:var(--bb-text-muted)]">optional</span>
</template>
<template #suffix>GB</template>
</BbNumberInput>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BbAsterisk, BbNumberInput } from 'bitboss-ui';
const quota = ref<number | null>(50);
const overage = ref<number | null>(null);
</script>
On the input family the slot scope is { text, hasErrors, hasWarnings }. On a
bitboss-ui/validated control you also get showAsterisk, already computed from
required, the rules and the plugin option. Bind to that instead of deciding
again:
<BbTextInput v-model="email" label="Work email" rules="required|email">
<template #label="{ text, showAsterisk }">
{{ text }}<BbAsterisk v-if="showAsterisk" />
<span class="ms-2 text-xs opacity-70">we never share it</span>
</template>
</BbTextInput>
Group controls (BbCheckboxGroup, BbRadioGroup, BbSwitchGroup) expose the
same scope on legend.
Colour and spacing
The component ships these declarations and inherits nothing else:
.bb-asterisk {
color: var(--bb-danger);
font-weight: 600;
margin-inline-start: 0.15em;
user-select: none;
}
The colour is --bb-danger: retheme that token and the marker follows everything
else that means "wrong". Do not hard-code a red here.
Use class or pt:root for a deliberate local adjustment. Neither changes the
field's required state.
Font size comes from the label, so the asterisk stays in proportion on a
compact field. If the default margin does not fit, wrap the component and space
that wrapper instead of overriding the class for one form.
If your product marks optional fields instead of required ones, do not
repurpose the asterisk. Write "optional", as the second field in the demo does. A
red * already means one thing to anyone who has filled in a form.