Input
Installation
Section titled “Installation”npx bambiui initnpx bambiui add inputThe CLI copies self-contained Input source into your project. The installed output includes framework source, recipe.ts, types.ts, and input.css.
Import global tokens once:
@import "./styles/bambi.css";Imports
Section titled “Imports”import { Input, InputField, InputControl, InputLabel, InputDescription, InputError, InputStart, InputEnd } from './components/ui/input';<script> import Input from './components/ui/Input.svelte'; import InputField from './components/ui/InputField.svelte';</script><script setup>import Input from './components/ui/Input.vue';import InputField from './components/ui/InputField.vue';</script>---import Input from './components/ui/Input.astro';import InputField from './components/ui/InputField.astro';---Control-only Usage
Section titled “Control-only Usage”Use Input alone when you supply your own accessible name via aria-label or a separate <label>.
Props-driven Field
Section titled “Props-driven Field”InputField renders a complete, labelled form field in a single component. Pass input attributes directly — they are forwarded to the native <input>.
<InputField label="Email address" description="We will never share your email." error={formErrors.email} type="email" name="email" required/><InputField label="Email address" description="We will never share your email." error={formErrors.email} type="email" name="email" required/><InputField label="Email address" description="We will never share your email." :error="formErrors.email" type="email" name="email" required/><InputField label="Email address" description="We will never share your email." type="email" name="email" required/>Label Modes
Section titled “Label Modes”Normal (default)
Section titled “Normal (default)”labelMode="normal" renders the label above the input. This is the default.
<InputField label="Full name" labelMode="normal" type="text" />Floating
Section titled “Floating”labelMode="floating" renders the label inside the input area. The label floats above when the field is focused or has a value. The label is still a real <label> element — it is never replaced by a placeholder.
<InputField label="Full name" labelMode="floating" type="text" />Requirements for floating labels:
- A
labelprop must be provided; the label element remains the accessible name. - Never use
placeholderas the accessible label. - If
placeholderis also set, it appears after the label has floated. - The
data-filledattribute on the field wrapper tracks value state. React, Svelte, and Vue track this reactively. Astro uses a progressive-enhancement inline script.
Variants
Section titled “Variants”| Variant | Description |
|---|---|
outline | Bordered input, default |
filled | Filled background, no border |
flushed | Bottom border only |
unstyled | No default styling |
<InputField label="Outline" variant="outline" type="text" /><InputField label="Filled" variant="filled" type="text" /><InputField label="Flushed" variant="flushed" type="text" /><InputField label="Unstyled" variant="unstyled" type="text" />| Size | Use case |
|---|---|
sm | Dense forms |
md | Default |
lg | Prominent fields |
<InputField label="Small" size="sm" type="text" /><InputField label="Medium" size="md" type="text" /><InputField label="Large" size="lg" type="text" />Use tone to communicate semantic feedback state.
| Tone | Meaning |
|---|---|
default | Neutral (default) |
danger | Error or invalid |
success | Valid |
warning | Caution |
<InputField label="Username" tone="success" type="text" /><InputField label="Password" tone="danger" error="Too short" type="password" /><InputField label="Recovery email" tone="warning" type="email" />When error is set, tone is automatically overridden to danger and aria-invalid="true" is applied to the input.
Start and End Adornments
Section titled “Start and End Adornments”Provide prefix and suffix content with start and end props (React) or slots (Svelte, Vue, Astro).
<InputField label="Website" start="https://" end=".com" type="url"/><InputField label="Website" type="url"> {#snippet start()}https://{/snippet} {#snippet end()}.com{/snippet}</InputField><InputField label="Website" type="url"> <template #start>https://</template> <template #end>.com</template></InputField><InputField label="Website" type="url"> <Fragment slot="start">https://</Fragment> <Fragment slot="end">.com</Fragment></InputField>Decorative icons in adornments should be aria-hidden="true". If an adornment is interactive (e.g., a reveal button), it must have an accessible name and be keyboard reachable.
Compound Usage
Section titled “Compound Usage”For advanced layouts, use the compound primitives directly. InputField provides context in React so InputLabel, InputDescription, and InputError receive their IDs automatically.
<InputField variant="filled" size="lg"> <InputLabel>Website</InputLabel> <InputControl hasStart hasEnd> <InputStart>https://</InputStart> <Input type="url" /> <InputEnd>.com</InputEnd> </InputControl> <InputDescription>Enter your site without the protocol.</InputDescription> <InputError>Please enter a valid URL.</InputError></InputField><!-- In Svelte, use InputField with children snippet for compound layout --><InputField variant="filled" size="lg"> {#snippet children()} <!-- manually compose structure here with HTML classes --> <label class="bambi-input-label" for="site">Website</label> <div class="bambi-input-control" data-has-start data-has-end> <div class="bambi-input-start">https://</div> <input id="site" type="url" class="bambi-input-element" /> <div class="bambi-input-end">.com</div> </div> <p class="bambi-input-description">Enter your site without the protocol.</p> {/snippet}</InputField><!-- In Vue, use InputField default slot for compound layout --><InputField variant="filled" size="lg"> <label class="bambi-input-label" for="site">Website</label> <div class="bambi-input-control" data-has-start data-has-end> <div class="bambi-input-start">https://</div> <input id="site" type="url" class="bambi-input-element" /> <div class="bambi-input-end">.com</div> </div> <p class="bambi-input-description">Enter your site without the protocol.</p></InputField><InputField variant="filled" size="lg"> <label class="bambi-input-label" for="site">Website</label> <div class="bambi-input-control" data-has-start data-has-end> <div class="bambi-input-start">https://</div> <input id="site" type="url" class="bambi-input-element" /> <div class="bambi-input-end">.com</div> </div> <p class="bambi-input-description">Enter your site without the protocol.</p></InputField>States
Section titled “States”Disabled
Section titled “Disabled”<InputField label="Username" type="text" disabled />Readonly
Section titled “Readonly”<InputField label="Account ID" type="text" value="acc_12345" readOnly />Required
Section titled “Required”<InputField label="Email" type="email" required />Required inputs receive the native required attribute. A visual * indicator is rendered after the label (aria-hidden). Native browser validation and AT communicate required semantics through the native attribute.
Invalid / Error
Section titled “Invalid / Error”<InputField label="Password" type="password" error="Password must be at least 8 characters"/>error sets tone="danger", aria-invalid="true" on the input, and renders the error message with role="alert" and an aria-describedby connection to the input.
API Reference
Section titled “API Reference”InputField Props
Section titled “InputField Props”| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible label text |
labelMode | 'normal' | 'floating' | 'normal' | Label placement behaviour |
description | string | — | Helper text below the input |
error | string | — | Error message; sets aria-invalid and tone="danger" |
variant | 'outline' | 'filled' | 'flushed' | 'unstyled' | 'outline' | Visual style |
size | 'sm' | 'md' | 'lg' | 'md' | Input height and font size |
tone | 'default' | 'danger' | 'success' | 'warning' | 'default' | Semantic colour state |
invalid | boolean | — | Marks input invalid without an error message |
fullWidth | boolean | false | Stretches field to container width |
required | boolean | — | Native required attribute + visual indicator |
disabled | boolean | — | Native disabled attribute |
readOnly | boolean | — | Native readonly attribute |
start | ReactNode (React) / slot (others) | — | Start adornment |
end | ReactNode (React) / slot (others) | — | End adornment |
All native <input> attributes (type, name, value, placeholder, autoComplete, onChange, etc.) are forwarded to the native element.
Input Props
Section titled “Input Props”Input accepts all native <input> attributes plus variant, size, tone, invalid, and fullWidth from InputBaseProps.
Data Attributes
Section titled “Data Attributes”Input styling is driven by stable data attributes on the field wrapper:
| Attribute | Source |
|---|---|
data-variant | variant |
data-size | size |
data-tone | computed tone (error overrides) |
data-label-mode | labelMode |
data-invalid | invalid or error |
data-disabled | disabled |
data-readonly | readOnly |
data-required | required |
data-full-width | fullWidth |
data-filled | input has a value |
data-has-start | start adornment present (on control) |
data-has-end | end adornment present (on control) |
Accessibility
Section titled “Accessibility”Labels
Section titled “Labels”Every input must have an accessible name. Use InputField with a label prop, or use Input with aria-label / an external <label>.
<!-- Preferred: visible label via InputField --><InputField label="Email" type="email" />
<!-- Acceptable: standalone Input with aria-label --><Input type="search" aria-label="Search" />Floating Labels
Section titled “Floating Labels”Floating labels are real <label> elements associated with the input via for/id. They must never be replaced by placeholder. Placeholder text is supplementary — it disappears when the user types and is not accessible to all AT. A floating label that has floated above the input is still the accessible name.
Descriptions and Errors
Section titled “Descriptions and Errors”Description and error text are connected to the input via aria-describedby. When both exist, both IDs are included.
<InputField label="Email" description="We will never share your email." error="Please enter a valid email address." type="email"/><!-- renders: aria-describedby="[desc-id] [err-id]" on the input -->aria-invalid
Section titled “aria-invalid”When error is provided or invalid is true, aria-invalid="true" is set on the native input. Assistive technology announces this to users.
Required
Section titled “Required”Native required is set on the input. The visual asterisk is aria-hidden="true" to avoid double-announcement.
Disabled and Readonly
Section titled “Disabled and Readonly”disableduses the nativedisabledattribute.readOnlyuses the nativereadonly/readOnlyattribute.- Focus remains possible on readonly inputs so users can still read and copy the value.
Placeholder is Not a Label
Section titled “Placeholder is Not a Label”Never use placeholder as the only accessible name for an input. The placeholder disappears once typing begins and has low colour contrast by design. Always provide a real <label>.