Input Component
Overview
Input is an accessible, theme-aware, validation-integrated form input component. It extends BaseComponent<InputProps> and is decorated with @Component({ selector: 'udiot-input' }). It injects both ThemeService and ValidationService via @InjectProperty(). When validationRules are provided, the component validates value on initialisation and renders an error message when validation fails. An optional AIStyler generates CSS at render time.
Key Concepts
- Renders an
<input>inside a.input-groupwrapper with an associated<label>and optional error message. - Full ARIA support:
aria-invalid,aria-describedby(pointing to the error element),required. - Validates against
ValidationRule[]using the injectedValidationService. - Integrates with
ThemeServicefor automatic light/dark styling. - Error display: either the explicit
errorprop or the result fromValidationServiceis shown as.input-error. createInput(props)factory for convenience instantiation.
API Reference
InputProps
interface InputProps {
type?: 'text' | 'email' | 'password' | 'number'; // Input type (default: 'text')
placeholder?: string; // Placeholder text
value?: string; // Initial value
disabled?: boolean; // Disable the input (default: false)
required?: boolean; // Mark as required (default: false)
label?: string; // Label text; rendered as <label> above input
error?: string; // Explicit error message to display
validationRules?: ValidationRule[]; // Rules applied via ValidationService on init
onChange?: (value: string) => void; // Change handler (client-side only)
aiStyler?: AIStyler; // AI styling bridge; omit for built-in styles
}
ValidationRule is defined in src/services/api/ — refer to the validation service documentation for the full rule shape.
Input
@Component({ selector: 'udiot-input' })
class Input extends BaseComponent<InputProps> {
constructor(props: InputProps);
render(): string; // Returns the full HTML string including <style>
}
Factory
function createInput(props: InputProps): Input;
Basic Usage
import { createInput } from '@tagna/udiot';
const nameInput = createInput({
type: 'text',
label: 'Full Name',
placeholder: 'Jane Smith',
required: true,
value: '',
});
console.log(nameInput.render());
// <style>.input-group { ... }</style>
// <div class="input-group">
// <label class="input-label" for="input-xyz">Full Name *</label>
// <input id="input-xyz" class="input-field" type="text" placeholder="Jane Smith"
// value="" required aria-invalid="false" aria-describedby="" />
// </div>
Common Patterns
Email input with error
const emailInput = createInput({
type: 'email',
label: 'Email Address',
placeholder: 'you@example.com',
required: true,
value: 'not-an-email',
error: 'Please enter a valid email address.',
});
// Renders with .has-error class on input and a visible .input-error div
Password input
const passwordInput = createInput({
type: 'password',
label: 'Password',
placeholder: 'At least 8 characters',
required: true,
});
Disabled input
const readOnlyField = createInput({
type: 'text',
label: 'Username',
value: 'johndoe',
disabled: true,
});
Validation rule shape (requires application integration)
import { createInput } from '@tagna/udiot';
import type { InputProps } from '@tagna/udiot';
const minLengthRule: NonNullable<InputProps['validationRules']>[number] = {
type: 'minLength',
value: 8,
message: 'Password must be at least 8 characters',
};
const passwordInput = createInput({
type: 'password',
label: 'New Password',
required: true,
value: 'abc', // Too short — ValidationService will set validationError
validationRules: [minLengthRule],
});
// Only if the internal validation service is wired, this can render: "Password must be at least 8 characters"
Composing a login form in SSR
import { createInput, createButton } from '@tagna/udiot';
export default {
render(): string {
const email = createInput({ type: 'email', label: 'Email', required: true });
const password = createInput({ type: 'password', label: 'Password', required: true });
const submit = createButton({ children: 'Sign In', variant: 'primary' });
return `
<form method="POST" action="/login">
${email.render()}
${password.render()}
${submit.render()}
</form>
`;
},
};
Integration Notes
- Each
Inputinstance generates a uniqueid(input-{random}) for the<label for>/<input id>association so label clicks correctly focus the input — even when multiple inputs are rendered on the same page. aria-invalidis set to"true"whenerrororvalidationErroris non-empty, satisfying WCAG 1.3.1 (Info and Relationships). The error element ID is used asaria-describedbyso screen readers announce the error message when the field is focused.ValidationService.validate(value, rules)is called synchronously insidevalidateValue(). If the service is not injected (manual instantiation outside DI), validation is skipped and the component renders without an error even ifvalidationRulesis provided.required: trueadds both the HTMLrequiredattribute and appends*to the label text. Browser-native validation is therefore active in addition toValidationServicevalidation.- The
onChangeprop is not wired into the rendered HTML; it is for client-side usage only. Usedata-event="change"/data-handler="myHandler"for SSR-compatible event delegation.
Pitfalls
labelis optional. When omitted, no<label>is rendered. Screen-reader users will lose the label association — always provide a label for accessible forms.errortakes precedence overvalidationErroronly in display priority: both are evaluated, but only one is shown. If you pass an expliciterrorprop, theValidationServiceresult is computed but not shown.- The generated
idis random (Math.random()). In SSR, the same component rendered on server and client will get different IDs, potentially causing a hydration mismatch warning in frameworks that verify DOM consistency. Pass a stableidvia a custom attribute or manage IDs in the page component if you need consistency. validationRulesare only evaluated on construction (insideonInit()). Ifvaluechanges after construction, re-validation does not happen automatically — create a newInputinstance or callvalidateValue()manually.
From the canonical UDIOT public documentation.Try it in the playground ↗