Forms
Each example imports from @static-bloom/vue, wraps its controls in SbProvider, and hydrates when visible. Vue setup explains the styles and provider.
Tables describe the wrapper’s additions and defaults, including theme defaults. Every component accepts a merged class (HTMLAttributes['class'], unset). Unless a table says otherwise, optional flags are false and optional values are unset. The remaining props and declared emits of each named Reka primitive pass through; use its root model with v-model and update:modelValue. Input and textarea expose their own native text model.
SbLabel + SbInput
Section titled “SbLabel + SbInput”SbLabel wraps Reka Label. SbInput renders an input through Primitive, or through FieldControl inside SbField, so labels, validation and field values stay connected.
Radio name: Courier
<script setup lang="ts">import { ref, useId } from 'vue'import { SbInput, SbLabel, SbProvider } from '@static-bloom/vue'const id = useId()const alias = ref<string | number>('Courier')</script>
<template> <SbProvider> <div class="w-full max-w-sm space-y-2"> <SbLabel :for="id">Call sign</SbLabel> <SbInput :id="id" v-model="alias" autocomplete="nickname" /> <p class="text-sm text-fg-muted">Radio name: {{ alias }}</p> </div> </SbProvider></template>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Label class |
HTMLAttributes['class'] |
unset | Merged label styling; remaining LabelProps pass through |
Input modelValue |
string | number |
unset | Controlled value |
Input defaultValue |
string | number |
'' effective fallback |
Initial uncontrolled value |
Input type |
'text' | 'email' | 'password' | 'search' | 'number' | 'tel' | 'url' |
'text' |
Native input type |
Input size |
'sm' | 'md' | 'lg' |
'md' |
Input height and type size |
Input invalid |
boolean |
false |
Sets aria-invalid |
Native attributes such as id, disabled, readonly, autocomplete and inputmode reach the input. The leading and trailing slots add non-interactive adornments. The model emits update:modelValue with a string or number; a native number input’s text edits are still strings.
Connect SbLabel’s for to the input’s id, or use SbFieldLabel in a field. Clicking the label focuses the input. Native text editing and Tab navigation remain available. Use selection and datetime components for in-game popups; the input’s type union deliberately excludes native date, time and color types.
SbInputGroup
Section titled “SbInputGroup”SbInputGroup wraps Reka Primitive to attach an input to SbInputGroupAddon text. It changes layout without replacing the input’s semantics.
<script setup lang="ts">import { useId } from 'vue'import { SbInput, SbInputGroup, SbInputGroupAddon, SbLabel, SbProvider } from '@static-bloom/vue'const id = useId()</script>
<template> <SbProvider> <div class="w-full max-w-sm space-y-2"> <SbLabel :for="id">Contract budget</SbLabel> <SbInputGroup> <SbInputGroupAddon>Credits</SbInputGroupAddon> <SbInput :id="id" default-value="1250" inputmode="decimal" /> <SbInputGroupAddon>.00</SbInputGroupAddon> </SbInputGroup> </div> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbInputGroup> <SbInputGroupAddon /> <SbInput /> <SbInputGroupAddon /></SbInputGroup>| Prop | Type | Default | Effect |
|---|---|---|---|
size |
'sm' | 'md' | 'lg' |
'md' |
Group input height |
Remaining PrimitiveProps pass through. Give the input its own label; addon text is not automatically a description or a label. The addon defaults to as="span". The input remains keyboard editable.
SbTextarea
Section titled “SbTextarea”SbTextarea renders a native textarea through Reka Primitive or FieldControl inside a field. autoResize measures the control on mount and input, growing it with the content.
<script setup lang="ts">import { useId } from 'vue'import { SbLabel, SbProvider, SbTextarea } from '@static-bloom/vue'const id = useId()</script>
<template> <SbProvider> <div class="w-full max-w-sm space-y-2"> <SbLabel :for="id">Delivery instructions</SbLabel> <SbTextarea :id="id" auto-resize placeholder="Where should the courier leave the parcel?" /> </div> </SbProvider></template>| Prop | Type | Default | Effect |
|---|---|---|---|
modelValue |
string | number |
unset | Controlled text |
defaultValue |
string | number |
'' effective fallback |
Initial text |
size |
'sm' | 'md' | 'lg' |
'md' |
Minimum height and type size |
invalid |
boolean |
false |
aria-invalid |
autoResize |
boolean |
false |
Content-driven height; disables manual resize |
It emits update:modelValue; native textarea attributes pass through. Enter inserts a line break, so do not use it as a form-wide submit shortcut. Label the control and let Tab move to the next field.
SbField + SbForm
Section titled “SbField + SbForm”SbField wraps Reka FieldRoot, with label, description and error parts. SbForm wraps FormRoot to collect named fields, validate on submission and focus the first invalid control.
<script setup lang="ts">import { ref } from 'vue'import { SbButton, SbField, SbFieldLabel, SbFieldDescription, SbFieldError, SbForm, SbInput, SbProvider,} from '@static-bloom/vue'const saved = ref(false)</script>
<template> <SbProvider> <SbForm class="w-full max-w-sm" @form-submit="saved = true"> <SbField name="callSign" required> <SbFieldLabel>Dispatch call sign</SbFieldLabel> <SbInput autocomplete="nickname" /> <SbFieldDescription>Use the name your crew recognizes.</SbFieldDescription> <SbFieldError /> </SbField> <SbButton type="submit" class="justify-self-start">Save call sign</SbButton> <p v-if="saved" role="status" class="text-sm text-success">Call sign saved.</p> </SbForm> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbForm> <SbField name="callSign" required> <SbFieldLabel /> <SbInput /> <SbFieldDescription /> <SbFieldError /> </SbField> <SbButton type="submit" /></SbForm>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Field orientation |
'horizontal' | 'vertical' |
'vertical' |
Field layout |
Form layout |
'stacked' | 'inline' | 'columns' |
'stacked' |
Form layout |
Remaining FieldRootProps, FormRootProps and declared form emits pass through. Put name, required, invalid or disabled on the field when they describe the whole field. The form’s formSubmit event receives valid named values; listen with @form-submit as shown.
The field associates label and description/error ids with its input. Invalid fields expose aria-invalid; disabled fields disable native controls. Required validation focuses the first invalid control on submit. Server errors can be displayed and clear when edited. Validation here is UI feedback; the server still owns acceptance of submitted data.
SbCheckbox + SbCheckboxGroup
Section titled “SbCheckbox + SbCheckboxGroup”SbCheckbox composes Reka CheckboxRoot and CheckboxIndicator. SbCheckboxGroup wraps CheckboxGroupRoot for a collection of selected values.
<script setup lang="ts">import { SbCheckbox, SbCheckboxGroup, SbProvider } from '@static-bloom/vue'</script>
<template> <SbProvider> <SbCheckboxGroup :default-value="['radio']" aria-label="Starting equipment"> <label class="inline-flex min-h-8 items-center gap-3 text-sm" ><SbCheckbox value="radio" aria-label="Radio" />Radio</label > <label class="inline-flex min-h-8 items-center gap-3 text-sm" ><SbCheckbox value="toolkit" aria-label="Toolkit" />Toolkit</label > </SbCheckboxGroup> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbCheckboxGroup> <SbCheckbox value="radio" /> <SbCheckbox value="toolkit" /></SbCheckboxGroup>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Checkbox size |
'sm' | 'md' | 'lg' |
'md' |
Checkbox dimensions |
Checkbox intent |
'primary' | 'secondary' | 'success' | 'danger' |
'primary' |
Checked color |
Checkbox invalid |
boolean |
false |
aria-invalid |
Group class |
HTMLAttributes['class'] |
unset | Layout classes; other props/emits are CheckboxGroupRootProps/emits |
Checkbox uses Reka’s modelValue/defaultValue, including 'indeterminate'; indeterminate is announced as aria-checked="mixed". Space toggles an enabled checkbox. Group items need distinct values and names; a group label does not label each checkbox. Disabled checkboxes cannot toggle.
SbRadioGroup
Section titled “SbRadioGroup”SbRadioGroup wraps Reka RadioGroupRoot; SbRadioGroupItem composes RadioGroupItem, RadioGroupIndicator and an associated label for its text slot.
<script setup lang="ts">import { SbProvider, SbRadioGroup, SbRadioGroupItem } from '@static-bloom/vue'</script>
<template> <SbProvider> <SbRadioGroup default-value="courier" aria-label="Contract type"> <SbRadioGroupItem value="courier">Courier</SbRadioGroupItem> <SbRadioGroupItem value="recovery">Recovery</SbRadioGroupItem> <SbRadioGroupItem value="restricted" disabled>Restricted</SbRadioGroupItem> </SbRadioGroup> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbRadioGroup> <SbRadioGroupItem value="courier">Courier</SbRadioGroupItem> <SbRadioGroupItem value="recovery">Recovery</SbRadioGroupItem></SbRadioGroup>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Group class |
HTMLAttributes['class'] |
unset | Root styling; remaining RadioGroupRootProps/emits pass through |
Item size |
'sm' | 'md' | 'lg' |
'md' |
Radio dimensions |
Item intent |
'primary' | 'secondary' | 'success' | 'danger' |
'primary' |
Selected color |
Item invalid |
boolean |
false |
aria-invalid |
Arrow keys focus and select the next radio; disabled options are unavailable. Use the group’s v-model for the selected value and aria-label or aria-labelledby for the question. Item slots provide their associated visible labels. Set orientation="horizontal" when options flow across a row.
SbSwitch
Section titled “SbSwitch”SbSwitch wraps Reka SwitchRoot and supplies SwitchThumb for a binary setting.
<script setup lang="ts">import { ref } from 'vue'import { SbProvider, SbSwitch } from '@static-bloom/vue'const enabled = ref(true)</script>
<template> <SbProvider> <label class="inline-flex min-h-10 items-center gap-3 text-sm"> <SbSwitch v-model="enabled" aria-label="Dispatch audio" />Dispatch audio {{ enabled ? 'on' : 'off' }} </label> </SbProvider></template>| Prop | Type | Default | Effect |
|---|---|---|---|
size |
'sm' | 'md' | 'lg' |
'md' |
Track and thumb dimensions |
intent |
'primary' | 'secondary' | 'success' | 'danger' |
'primary' |
On-state color |
invalid |
boolean |
false |
aria-invalid |
Remaining SwitchRootProps and emits pass through. v-model is the on/off value; Space toggles the named switch and updates aria-checked. Disabled switches expose their disabled state. Give it a stable setting name, independent of the current on/off value.
SbSlider
Section titled “SbSlider”SbSlider wraps Reka SliderRoot and supplies track, range and one thumb per value. Its default slot can replace that composition.
Radio volume: 60
<script setup lang="ts">import { ref } from 'vue'import { SbProvider, SbSlider } from '@static-bloom/vue'const volume = ref<number[] | null>([60])</script>
<template> <SbProvider> <div class="w-full max-w-sm space-y-3"> <p class="text-sm text-fg-muted">Radio volume: {{ volume?.[0] }}</p> <SbSlider v-model="volume" :thumb-labels="['Radio volume']" /> </div> </SbProvider></template>| Prop | Type | Default | Effect |
|---|---|---|---|
size |
'sm' | 'md' | 'lg' |
'md' |
Track and thumb dimensions |
thumbLabels |
readonly string[] |
unset | Each thumb’s accessible name; fallback Value 1, Value 2, etc. |
Remaining SliderRootProps and emits pass through. The model is an array, even for one thumb. Use :default-value="[20, 80]" for a range. ArrowRight changes the value; Home/End move to the bounds. Each thumb exposes aria-valuenow. A disabled vertical slider removes thumbs from tab order. Name both ends of a range distinctly.
For game settings, coordinate pointer focus with the game client so dragging a slider does not also move the camera. Keep a numeric alternative available when exact entry matters.
SbNumberField
Section titled “SbNumberField”SbNumberField wraps Reka NumberFieldRoot and supplies decrement, input and increment parts by default. Compose those exported parts yourself when their order or labels need to change.
<script setup lang="ts">import { SbNumberField, SbProvider } from '@static-bloom/vue'</script>
<template> <SbProvider> <div class="w-full max-w-xs"> <SbNumberField label="Crew capacity" :default-value="3" :min="1" :max="8" /> </div> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbNumberField> <SbNumberFieldDecrement /> <SbNumberFieldInput /> <SbNumberFieldIncrement /></SbNumberField>| Prop | Type | Default | Effect |
|---|---|---|---|
size |
'sm' | 'md' | 'lg' |
'md' |
Inherited input size |
label |
string |
'Value' |
Root and default input accessible name |
invalid |
boolean |
false |
aria-invalid on the default input |
Remaining NumberFieldRootProps/emits pass through, including bounds and numeric v-model. Increase/Decrease controls default to the labels Increase and Decrease. ArrowUp increments; Home/End use the configured min/max. The input exposes the spinbutton role. With custom composition, label the input and carry its invalid state yourself.
SbPinInput
Section titled “SbPinInput”SbPinInput wraps Reka PinInputRoot. Add one SbPinInputInput per character, with zero-based indexes.
<script setup lang="ts">import { SbPinInput, SbPinInputInput, SbProvider } from '@static-bloom/vue'</script>
<template> <SbProvider> <SbPinInput aria-label="Depot access code" placeholder="-" otp> <SbPinInputInput v-for="index in 4" :key="index" :index="index - 1" :aria-label="`Access code digit ${index}`" /> </SbPinInput> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbPinInput> <SbPinInputInput :index="0" /> <SbPinInputInput :index="1" /></SbPinInput>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Root class |
HTMLAttributes['class'] |
unset | Root styling; PinInputRootProps and emits pass through |
Input size |
'sm' | 'md' | 'lg' |
'md' |
Character box dimensions |
Entering a character advances focus. The root’s mask uses password inputs, and disabled reaches the inputs. otp enables one-time-code usage. Name the group and its digit controls; use the root model for the character array. A visible PIN UI is not itself an authentication boundary.
SbTagsInput
Section titled “SbTagsInput”SbTagsInput wraps Reka TagsInputRoot for editing a list of values. Text, delete and clear parts keep the displayed tags tied to the root model.
<script setup lang="ts">import { ref } from 'vue'import { SbProvider, SbTagsInput, SbTagsInputItem, SbTagsInputItemText, SbTagsInputItemDelete, SbTagsInputInput, SbTagsInputClear,} from '@static-bloom/vue'const skills = ref(['Driving', 'Repair'])</script>
<template> <SbProvider> <SbTagsInput v-model="skills" class="w-full max-w-sm"> <SbTagsInputItem v-for="skill in skills" :key="skill" :value="skill"> <SbTagsInputItemText /><SbTagsInputItemDelete :label="`Remove ${skill}`" /> </SbTagsInputItem> <SbTagsInputInput aria-label="Crew skills" placeholder="Add skill, then Enter" /> <SbTagsInputClear /> </SbTagsInput> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbTagsInput> <SbTagsInputItem value="Driving"> <SbTagsInputItemText /> <SbTagsInputItemDelete /> </SbTagsInputItem> <SbTagsInputInput /> <SbTagsInputClear /></SbTagsInput>| Prop | Type | Default | Effect |
|---|---|---|---|
size |
'sm' | 'md' | 'lg' |
'md' |
Root dimensions |
Remaining generic TagsInputRootProps<T>/emits pass through; T is string by default and can also be number, bigint or a record. Enter adds a typed tag. The delete part removes its value, and Clear empties the model. Their default labels are Remove tag and Clear tags; use value-specific removal names. Label the text input separately from its placeholder.
SbEditable
Section titled “SbEditable”SbEditable wraps Reka EditableRoot for an inline preview/edit cycle. Its exported area, preview, input and trigger parts let the consumer make edit and commit controls explicit.
<script setup lang="ts">import { SbEditable, SbEditableArea, SbEditablePreview, SbEditableInput, SbEditableEditTrigger, SbEditableSubmitTrigger, SbEditableCancelTrigger, SbProvider,} from '@static-bloom/vue'</script>
<template> <SbProvider> <SbEditable default-value="Harbor crew" activation-mode="none" submit-mode="enter" role="group" aria-label="Crew editor" class="w-full max-w-sm" > <SbEditableArea class="basis-full"> <SbEditablePreview class="[&[hidden]]:hidden" /> <SbEditableInput label="Crew name" class="[&[hidden]]:hidden" /> </SbEditableArea> <SbEditableEditTrigger class="bg-panel [&[hidden]]:hidden" /> <SbEditableSubmitTrigger class="bg-panel [&[hidden]]:hidden" /> <SbEditableCancelTrigger class="bg-panel [&[hidden]]:hidden" /> </SbEditable> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbEditable> <SbEditableArea> <SbEditablePreview /> <SbEditableInput /> </SbEditableArea> <SbEditableEditTrigger /> <SbEditableSubmitTrigger /> <SbEditableCancelTrigger /></SbEditable>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Root class |
HTMLAttributes['class'] |
unset | Root styling; EditableRootProps/emits pass through |
Input size |
'sm' | 'md' | 'lg' |
'md' |
Editor dimensions |
Input label |
string |
'Editable text' |
Input accessible name |
Edit / Submit / Cancel trigger label |
string |
'Edit' / 'Submit' / 'Cancel' |
Accessible name and fallback text |
The example explicitly sets activation-mode="none" and submit-mode="enter". Its [&[hidden]]:hidden utilities preserve Reka’s hidden state when component display classes override the browser’s default hidden rule. Edit starts entry, Submit commits, and Escape cancels edits. Give the editor a name describing the record. In-game Escape handling depends on the page’s keep-input configuration; retain the visible Cancel trigger.
SbTagGroup
Section titled “SbTagGroup”SbTagGroup wraps Reka TagGroupRoot for selecting and removing existing tags. Unlike tags input, it has no text-entry field.
<script setup lang="ts">import { ref } from 'vue'import { SbProvider, SbTagGroup, SbTagGroupItem, SbTagGroupItemText, SbTagGroupItemDelete,} from '@static-bloom/vue'const districts = ref(['Harbor', 'North depot', 'Old quarter'])function remove(values: unknown[]) { districts.value = districts.value.filter((value) => !values.includes(value))}</script>
<template> <SbProvider> <SbTagGroup selection-mode="multiple" aria-label="Active districts" @remove="remove"> <SbTagGroupItem v-for="district in districts" :key="district" :value="district" :text-value="district" > <SbTagGroupItemText>{{ district }}</SbTagGroupItemText> <SbTagGroupItemDelete :label="`Remove ${district}`" /> </SbTagGroupItem> </SbTagGroup> </SbProvider></template>Anatomy
Section titled “Anatomy”<SbTagGroup> <SbTagGroupItem value="Harbor"> <SbTagGroupItemText /> <SbTagGroupItemDelete /> </SbTagGroupItem></SbTagGroup>| Component / prop | Type | Default | Effect |
|---|---|---|---|
Root class |
HTMLAttributes['class'] |
unset | Root styling; remaining TagGroupRootProps/emits pass through |
ItemDelete label |
string |
'Remove' |
Delete control name |
The root model tracks selected values. A remove event supplies values for the consumer to remove from its own collection, as shown above. The tests exercise keyboard selection and Delete removal. Label the group and give each item readable text; disable locked tags instead of removing their explanation.
All these controls render in the document and need WebUI focus to receive keyboard input in OPEN//77. Keep game shortcuts from interfering while a text control is focused, and call the platform’s ready hook after the app mounts. See In-game UIs.