A versatile input component that provides a clean interface for text input with support for labels, validation, start/end content, and a clearable option.
import { Input } from 'frontile';
The most basic usage of the Input component with only a label.
import { Input } from 'frontile';
<template><Input @label='Full Name' /></template>
You can control the input value using the Form component's data binding pattern. The Form automatically manages the input's value and updates.
Current value:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Form, type FormResultData } from 'frontile';
export default class ControlledInput extends Component {
@tracked formData = { name: '' };
handleFormChange = (result: FormResultData) => {
this.formData = result.data;
};
<template>
<div class='flex flex-col gap-4'>
<Form @data={{this.formData}} @onChange={{this.handleFormChange}} as |form|>
<form.Field @name='name' as |field|>
<field.Input @label='Your Name' />
</form.Field>
</Form>
<p>Current value: {{this.formData.name}}</p>
</div>
</template>
}
The Input component supports different HTML input types through the @type argument.
import Component from '@glimmer/component';
import { Input } from 'frontile';
export default class InputTypes extends Component {
<template>
<div class='flex flex-col gap-4'>
<Input @label='Email' @type='email' />
<Input @label='Password' @type='password' />
<Input @label='Phone Number' @type='tel' />
<Input @label='Age' @type='number' />
<Input @label='Website' @type='url' />
</div>
</template>
}
You can add custom content at the beginning or end of the input using named blocks.
import Component from '@glimmer/component';
import { Input } from 'frontile';
import { SearchIcon } from 'site/components/icons';
export default class InputWithContent extends Component {
<template>
<div class='flex flex-col gap-4'>
<Input @label='Price'>
<:startContent>
<span class='text-neutral-soft'>$</span>
</:startContent>
<:endContent>
<span class='text-neutral-soft'>USD</span>
</:endContent>
</Input>
<Input @label='Search'>
<:startContent>
<SearchIcon />
</:startContent>
<:endContent>
<button type='button'>Go</button>
</:endContent>
</Input>
</div>
</template>
}
When using interactive content in start or end slots, you can control pointer events to ensure proper click handling.
import Component from '@glimmer/component';
import { Input } from 'frontile';
import { on } from '@ember/modifier';
import { SearchIcon } from 'site/components/icons';
export default class SearchInput extends Component {
handleSearch = () => {
console.log('Search clicked');
};
<template>
<Input
@label='Search with Button'
@startContentPointerEvents='none'
@endContentPointerEvents='auto'
>
<:startContent>
<SearchIcon />
</:startContent>
<:endContent>
<button type='button' {{on 'click' this.handleSearch}}>
Search
</button>
</:endContent>
</Input>
</template>
}
Enable a clear button that appears when the input has a value by setting @isClearable to true.
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Form, type FormResultData } from 'frontile';
export default class ClearableInput extends Component {
@tracked formData = { searchQuery: '' };
handleFormChange = (result: FormResultData) => {
this.formData = result.data;
};
<template>
<Form @data={{this.formData}} @onChange={{this.handleFormChange}} as |form|>
<form.Field @name='searchQuery' as |field|>
<field.Input
@label='Search Query'
@isClearable={{true}}
placeholder='Type to search...'
/>
</form.Field>
</Form>
</template>
}
The Input component integrates with form validation by displaying error messages and updating ARIA attributes accordingly. This example demonstrates validation using Valibot schema with the Form/Field components.
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { Form, type FormResultData, type FormErrors } from 'frontile';
import { Button } from 'frontile';
import * as v from 'valibot';
// Define validation schema
const schema = v.object({
name: v.pipe(
v.string(),
v.nonEmpty('Name is required'),
v.minLength(2, 'Name must be at least 2 characters')
),
email: v.pipe(
v.string(),
v.nonEmpty('Email is required'),
v.email('Please enter a valid email address')
)
});
type Schema = v.InferOutput<typeof schema>;
export default class ValidatedInput extends Component {
@tracked formData: Schema = {
name: '',
email: ''
};
handleFormChange = (result: FormResultData<Schema>) => {
this.formData = result.data;
};
handleFormSubmit = (result: FormResultData<Schema>) => {
console.log('Form submitted:', result.data);
};
handleFormError = (errors: FormErrors) => {
console.log('Validation errors:', errors);
};
<template>
<Form
@data={{this.formData}}
@schema={{schema}}
@onChange={{this.handleFormChange}}
@onSubmit={{this.handleFormSubmit}}
@onError={{this.handleFormError}}
as |form|
>
<form.Field @name='name' as |field|>
<field.Input @label='Name' @isRequired={{true}} />
</form.Field>
<form.Field @name='email' as |field|>
<field.Input
@label='Email Address'
@type='email'
@isRequired={{true}}
/>
</form.Field>
<Button type='submit'>Submit</Button>
</Form>
</template>
}
The Input component supports various optional configurations for sizing, states, and styling.
import { Input } from 'frontile';
import { hash } from '@ember/helper';
<template>
<div class='flex flex-col gap-6'>
{{! Size variants }}
<div>
<h4 class='text-sm font-medium mb-2'>Size Variants</h4>
<div class='flex flex-col gap-3'>
<Input @label='Small Input' @size='sm' />
<Input @label='Medium Input' @size='md' />
<Input @label='Large Input' @size='lg' />
</div>
</div>
{{! Disabled state }}
<div>
<h4 class='text-sm font-medium mb-2'>Disabled State</h4>
<div class='flex flex-col gap-3'>
<Input
@label='Disabled Input'
@value='Cannot edit this text'
disabled={{true}}
/>
<Input
@label='Disabled with Description'
@description='This input is disabled'
@value='Sample value'
disabled={{true}}
/>
</div>
</div>
{{! With description }}
<div>
<h4 class='text-sm font-medium mb-2'>With Description</h4>
<Input
@label='Username'
@description='Choose a unique username'
/>
</div>
{{! Custom styling }}
<div>
<h4 class='text-sm font-medium mb-2'>Custom Styling</h4>
<Input
@label='Custom Classes'
@classes={{hash
base='my-custom-form-control'
input='my-custom-input-field'
}}
/>
</div>
</div>
</template>
The Input component follows accessibility best practices:
for and id attributesaria-invalid)aria-describedbyElement: <span class="hljs-title class_">HTMLInputElement</span>
| Name | Type | Default | Description |
|---|---|---|---|
classes
|
SlotsToClasses<'base' | 'input' | 'innerContainer' | 'startContent' | 'endContent'>
|
- | Class names for each slot of the component, merged with the theme's. |
description
|
string
|
- |
Help text rendered between the label and the control, and referenced by the
ids describedBy returns.
|
endContentPointerEvents
|
enum
|
'auto'
|
Controls pointer-events property of endContent.
If you want to pass the click event to the input, set it to none.
|
errors
|
enum
|
- |
Validation messages for the field. A non-empty value also marks the control
invalid, and an array is joined with ; when displayed.
|
isClearable
|
boolean
|
- | Whether to include a clear button |
isDisabled
|
boolean
|
false
|
Whether the field is disabled. FormControl passes this through for styling;
the control it wraps is responsible for the disabled attribute.
|
isInvalid
|
boolean
|
false
|
Marks the control invalid without supplying messages, for validation that is reported elsewhere. |
isRequired
|
boolean
|
false
|
Whether the field is required. Adds an asterisk to the label; it does not
set the required attribute on the control itself.
|
label
|
string
|
- |
The label text rendered above the control and associated with it via for.
Use the :label block instead when the label needs markup.
|
name
|
string
|
- | The name attribute of the underlying input, used when the input is submitted as part of a form. |
onBlur
|
function
|
- | Callback when onblur is triggered |
onChange
|
function
|
- | Callback when onchange is triggered |
onInput
|
function
|
- | Callback when oninput is triggered |
size
|
enum
|
'md'
|
The size of the input and its label. |
startContentPointerEvents
|
enum
|
'auto'
|
Controls pointer-events property of startContent.
If you want to pass the click event to the input, set it to none.
|
type
|
string
|
'text'
|
The type attribute of the underlying input element. |
value
|
string
|
- |
The value of the input. Pair with onInput or onChange to control the
input; leave it unset to let the element track its own value.
|
| Name | Type | Default | Description |
|---|---|---|---|
startContent
*
|
Array
|
- | |
endContent
*
|
Array
|
- |