@isa/ui/input-controls
A comprehensive collection of form input components and directives for Angular applications supporting reactive forms, template-driven forms, and accessibility features.
Overview
The Input Controls library provides a complete suite of reusable form components built on Angular's reactive forms API and Angular CDK. It includes text fields, checkboxes, dropdowns, chips, checklists, listboxes, and inline inputs - all designed to work seamlessly with Angular Forms while maintaining consistent styling and behavior across the ISA application.
Table of Contents
- Features
- Quick Start
- Core Concepts
- API Reference
- Usage Examples
- Form Integration
- Accessibility
- Testing
- Architecture Notes
Features
- Complete form control suite - 8 component types covering common input scenarios
- Reactive Forms integration - ControlValueAccessor implementation for all form controls
- Template-driven forms - Full ngModel support for two-way binding
- Keyboard navigation - Arrow keys, Enter, Escape, and Tab support
- Accessibility (a11y) - ARIA attributes, screen reader support, keyboard navigation
- Customizable appearance - Multiple size and appearance variants for each control
- Validation support - Integration with Angular's built-in validators
- CDK integration - Built on Angular CDK primitives (listbox, a11y, overlay)
- Signal-based inputs - Modern Angular signals for reactive property management
- Type-safe - Full TypeScript support with generic type parameters
Quick Start
1. Import Components
import { Component } from '@angular/core';
import {
CheckboxComponent,
DropdownButtonComponent,
DropdownFilterComponent,
DropdownOptionComponent,
TextFieldComponent,
InputControlDirective,
} from '@isa/ui/input-controls';
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-my-form',
imports: [
ReactiveFormsModule,
CheckboxComponent,
DropdownButtonComponent,
DropdownFilterComponent,
DropdownOptionComponent,
TextFieldComponent,
InputControlDirective,
],
template: '...'
})
export class MyFormComponent {}
2. Basic Text Field
<ui-text-field>
<input
type="text"
uiInputControl
formControlName="username"
placeholder="Enter username"
/>
</ui-text-field>
3. Checkbox
<ui-checkbox>
<input type="checkbox" formControlName="acceptTerms" />
</ui-checkbox>
4. Dropdown
<ui-dropdown [(ngModel)]="selectedOption" label="Select option">
<ui-dropdown-option [value]="1">Option 1</ui-dropdown-option>
<ui-dropdown-option [value]="2">Option 2</ui-dropdown-option>
<ui-dropdown-option [value]="3">Option 3</ui-dropdown-option>
</ui-dropdown>
5. Dropdown with Filter
<ui-dropdown [(ngModel)]="selectedCountry" label="Select country">
<ui-dropdown-filter placeholder="Search..."></ui-dropdown-filter>
<ui-dropdown-option [value]="'de'">Germany</ui-dropdown-option>
<ui-dropdown-option [value]="'at'">Austria</ui-dropdown-option>
<ui-dropdown-option [value]="'ch'">Switzerland</ui-dropdown-option>
</ui-dropdown>
Core Concepts
Component Categories
The library provides three main categories of form controls:
1. Text Input Controls
- TextFieldComponent - Standard text input with container, labels, and errors
- TextareaComponent - Multi-line text input
- InlineInputComponent - Compact inline text input for space-constrained UIs
2. Selection Controls
- CheckboxComponent - Single checkbox with bullet or checkbox appearance
- ChecklistComponent - Multiple checkboxes working as a group
- DropdownButtonComponent - Select dropdown with keyboard navigation
- ChipsComponent - Single-select chip group
- ListboxDirective - CDK listbox wrapper for custom list selections
3. Supporting Components
- InputControlDirective - Core directive for input field integration
- TextFieldContainerComponent - Layout container for text fields
- TextFieldErrorsComponent - Validation error display
- TextFieldClearComponent - Clear button for text inputs
- CheckboxLabelDirective - Label styling for checkboxes
- ChecklistValueDirective - Value binding for checklist items
- ChipOptionComponent - Individual chip option
- DropdownOptionComponent - Individual dropdown option
- DropdownFilterComponent - Filter input for dropdown options
- ListboxItemDirective - Individual listbox item
Control Value Accessor Pattern
All form controls implement Angular's ControlValueAccessor interface, enabling seamless integration with both reactive and template-driven forms:
export class DropdownButtonComponent<T> implements ControlValueAccessor {
value = model<T>();
disabled = model<boolean>(false);
writeValue(obj: unknown): void { ... }
registerOnChange(fn: (value: T) => void): void { ... }
registerOnTouched(fn: () => void): void { ... }
setDisabledState(isDisabled: boolean): void { ... }
}
Appearance Variants
Checkbox Appearances
const CheckboxAppearance = {
Bullet: 'bullet', // Round bullet-style selector
Checkbox: 'checkbox', // Traditional square checkbox (default)
} as const;
Text Field Sizes
const TextFieldSize = {
Small: 'small',
Medium: 'medium', // Default
Large: 'large',
} as const;
API Reference
CheckboxComponent
A customizable checkbox component supporting different visual appearances.
Selector: ui-checkbox
Inputs:
appearance: CheckboxAppearance- Visual style ('checkbox' | 'bullet'). Default: 'checkbox'
Usage:
<!-- Default checkbox appearance -->
<ui-checkbox>
<input type="checkbox" />
</ui-checkbox>
<!-- Bullet appearance -->
<ui-checkbox [appearance]="CheckboxAppearance.Bullet">
<input type="checkbox" />
</ui-checkbox>
DropdownButtonComponent
A dropdown select component with keyboard navigation and CDK overlay integration.
Selector: ui-dropdown
Inputs:
appearance: DropdownAppearance- Visual style. Default: 'accent-outline'label: string- Dropdown label textshowSelectedValue: boolean- Show selected option text. Default: truetabIndex: number- Tab index for keyboard navigation. Default: 0id: string- Optional element IDequals: (a: T | null, b: T | null) => boolean- Custom equality function for comparing option values. Default:lodash.isEqual
Outputs:
value: ModelSignal<T>- Two-way bindable selected valuedisabled: ModelSignal<boolean>- Disabled state
Methods:
open(): void- Opens the dropdown overlayclose(): void- Closes the dropdown overlayselect(option, options?): void- Selects an option programmatically
Keyboard Navigation:
Arrow Down/Up- Navigate through optionsEnter- Select highlighted option and closeEscape- Close dropdownSpace- Toggle dropdown open/close (when focused)
Usage:
interface Product {
id: number;
name: string;
}
products: Product[] = [
{ id: 1, name: 'Product A' },
{ id: 2, name: 'Product B' }
];
selectedProduct: Product;
<ui-dropdown
[(value)]="selectedProduct"
label="Select Product"
appearance="accent-outline">
@for (product of products; track product.id) {
<ui-dropdown-option [value]="product">
{{ product.name }}
</ui-dropdown-option>
}
</ui-dropdown>
Custom Equality Comparison:
When working with object values, you may need custom comparison logic (e.g., comparing by ID instead of deep equality):
// Compare products by ID instead of deep equality
readonly productEquals = (a: Product | null, b: Product | null) => a?.id === b?.id;
<ui-dropdown
[(value)]="selectedProduct"
[equals]="productEquals"
label="Select Product">
@for (product of products; track product.id) {
<ui-dropdown-option [value]="product">
{{ product.name }}
</ui-dropdown-option>
}
</ui-dropdown>
DropdownFilterComponent
A filter input component for use within a DropdownButtonComponent. Renders as a sticky input at the top of the options panel, allowing users to filter options by typing.
Selector: ui-dropdown-filter
Inputs:
placeholder: string- Placeholder text for the filter input. Default: 'Suchen...'
Features:
- Sticky positioning at top of options panel
- Auto-focuses when dropdown opens
- Clears automatically when dropdown closes
- Arrow key navigation works while typing
- Enter key selects the highlighted option
Usage:
<ui-dropdown [(ngModel)]="selectedCountry" label="Select country">
<ui-dropdown-filter placeholder="Search countries..."></ui-dropdown-filter>
<ui-dropdown-option [value]="'de'">Germany</ui-dropdown-option>
<ui-dropdown-option [value]="'at'">Austria</ui-dropdown-option>
<ui-dropdown-option [value]="'ch'">Switzerland</ui-dropdown-option>
<ui-dropdown-option [value]="'fr'">France</ui-dropdown-option>
<ui-dropdown-option [value]="'it'">Italy</ui-dropdown-option>
</ui-dropdown>
Keyboard Navigation (when filter is focused):
Arrow Down/Up- Navigate through optionsEnter- Select highlighted option and closeSpace- Types in the input (does not toggle dropdown)Escape- Keeps focus in input
DropdownOptionComponent
A selectable option component for use within a DropdownButtonComponent. Implements the CDK Highlightable interface for keyboard navigation support.
Selector: ui-dropdown-option
Inputs:
| Input | Type | Required | Default | Description |
|---|---|---|---|---|
value |
T |
Yes | - | The value associated with this option |
disabled |
boolean |
No | false |
Whether this option is disabled |
Usage:
<ui-dropdown [(ngModel)]="selected">
<ui-dropdown-option [value]="'opt1'">Option 1</ui-dropdown-option>
<ui-dropdown-option [value]="'opt2'">Option 2</ui-dropdown-option>
<ui-dropdown-option [value]="'opt3'" [disabled]="true">Disabled</ui-dropdown-option>
</ui-dropdown>
Features:
- Uses host dropdown's
equalsfunction for value comparison (defaults tolodash.isEqual) - Automatic filtering support when used with
DropdownFilterComponent - CSS classes applied automatically:
active,selected,disabled,filtered - ARIA
role="option"andaria-selectedattributes for accessibility
ChipsComponent
Single-selection chip group with form integration.
Selector: ui-chips
Outputs:
value: ModelSignal<T>- Selected chip valuedisabled: ModelSignal<boolean>- Disabled state
Methods:
select(value: T, options?): void- Selects a chiptoggle(value: T, options?): void- Toggles chip selectionisSelected(value: T): boolean- Checks if value is selected
Usage:
type Size = 'S' | 'M' | 'L' | 'XL';
selectedSize: Size = 'M';
<ui-chips [(ngModel)]="selectedSize">
<ui-chip [value]="'S'">Small</ui-chip>
<ui-chip [value]="'M'">Medium</ui-chip>
<ui-chip [value]="'L'">Large</ui-chip>
<ui-chip [value]="'XL'">Extra Large</ui-chip>
</ui-chips>
ChecklistComponent
Multi-select checkbox group returning an array of selected values.
Selector: ui-checklist
Outputs:
value: ModelSignal<unknown[]>- Array of selected values
Usage:
selectedFruits: string[] = ['apple', 'banana'];
<ui-checklist [(ngModel)]="selectedFruits">
<label class="ui-checkbox-label">
<ui-checkbox>
<input type="checkbox" [uiChecklistValue]="'apple'">
</ui-checkbox>
Apple
</label>
<label class="ui-checkbox-label">
<ui-checkbox>
<input type="checkbox" [uiChecklistValue]="'banana'">
</ui-checkbox>
Banana
</label>
</ui-checklist>
TextFieldComponent
Container component for text input fields with consistent styling.
Selector: ui-text-field
Inputs:
size: TextFieldSize- Field size ('small' | 'medium' | 'large'). Default: 'medium'
Content Projection:
- Requires
InputControlDirectiveon child input element
Usage:
<ui-text-field size="medium">
<input
type="text"
uiInputControl
formControlName="email"
placeholder="Enter email"
/>
</ui-text-field>
InlineInputComponent
Compact inline text input for space-constrained interfaces.
Selector: ui-inline-input
Inputs:
size: InlineInputSize- Field size ('small' | 'medium'). Default: 'medium'
Usage:
<ui-inline-input size="small">
<input type="text" uiInputControl [(ngModel)]="quantity" />
</ui-inline-input>
ListboxDirective
Wrapper directive for Angular CDK listbox with ISA styling.
Selector: [uiListbox]
Inputs (via CDK):
value: any- Selected value(s)compareWith: (o1: any, o2: any) => boolean- Comparison functiondisabled: boolean- Disabled state
Outputs (via CDK):
valueChange: EventEmitter- Emits when selection changes
Usage:
<div uiListbox [value]="selectedItem" (valueChange)="handleChange($event)">
<div uiListboxItem [value]="item1">Item 1</div>
<div uiListboxItem [value]="item2">Item 2</div>
</div>
Usage Examples
Reactive Form with Validation
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import {
TextFieldComponent,
TextFieldContainerComponent,
TextFieldErrorsComponent,
InputControlDirective,
CheckboxComponent,
DropdownButtonComponent,
DropdownOptionComponent
} from '@isa/ui/input-controls';
@Component({
selector: 'app-user-form',
imports: [
ReactiveFormsModule,
TextFieldComponent,
TextFieldContainerComponent,
TextFieldErrorsComponent,
InputControlDirective,
CheckboxComponent,
DropdownButtonComponent,
DropdownOptionComponent
],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<ui-text-field-container>
<label>Email</label>
<ui-text-field>
<input
type="email"
uiInputControl
formControlName="email"
placeholder="user@example.com"
/>
</ui-text-field>
<ui-text-field-errors />
</ui-text-field-container>
<ui-dropdown formControlName="role" label="Select Role">
<ui-dropdown-option [value]="'admin'">Administrator</ui-dropdown-option>
<ui-dropdown-option [value]="'user'">User</ui-dropdown-option>
</ui-dropdown>
<label class="ui-checkbox-label">
<ui-checkbox>
<input type="checkbox" formControlName="acceptTerms" />
</ui-checkbox>
I accept the terms and conditions
</label>
<button type="submit" [disabled]="!userForm.valid">Submit</button>
</form>
`
})
export class UserFormComponent {
#fb = inject(FormBuilder);
userForm = this.#fb.group({
email: ['', [Validators.required, Validators.email]],
role: ['user', Validators.required],
acceptTerms: [false, Validators.requiredTrue]
});
onSubmit() {
if (this.userForm.valid) {
console.log('Form submitted:', this.userForm.value);
}
}
}
Form Integration
Reactive Forms
All components implement ControlValueAccessor and work with FormControl:
import { FormControl, Validators } from '@angular/forms';
emailControl = new FormControl('', [Validators.required, Validators.email]);
roleControl = new FormControl<string>('user');
agreeControl = new FormControl(false, Validators.requiredTrue);
<ui-text-field>
<input uiInputControl [formControl]="emailControl" />
</ui-text-field>
<ui-dropdown [formControl]="roleControl" label="Role">...</ui-dropdown>
<ui-checkbox><input [formControl]="agreeControl" /></ui-checkbox>
Template-Driven Forms
Use ngModel for two-way binding:
<ui-text-field>
<input uiInputControl [(ngModel)]="email" name="email" />
</ui-text-field>
<ui-dropdown [(ngModel)]="role" name="role" label="Role">...</ui-dropdown>
Accessibility
Keyboard Navigation
All components support comprehensive keyboard navigation:
Dropdown:
Arrow Down/Up- Navigate through optionsEnter- Select highlighted option and closeEscape- Close dropdown without selectionTab- Move focus to next element
Listbox:
Arrow Down/Up- Navigate itemsHome/End- Jump to first/last itemSpace/Enter- Select item
Checkbox/Checklist:
Space- Toggle checkbox stateTab- Move between checkboxes
ARIA Attributes
Components include proper ARIA attributes for screen readers:
<!-- Dropdown -->
<ui-dropdown
role="combobox"
aria-haspopup="listbox"
[attr.aria-expanded]="isOpen()">
</ui-dropdown>
<!-- Listbox -->
<div uiListbox role="listbox">
<div uiListboxItem role="option"></div>
</div>
Testing
The library uses Jest with Spectator for testing.
Running Tests
# Run tests for this library
npx nx test ui-input-controls --skip-nx-cache
# Run tests with coverage
npx nx test ui-input-controls --code-coverage --skip-nx-cache
# Run tests in watch mode
npx nx test ui-input-controls --watch
Test Coverage
The library includes comprehensive unit tests covering:
- Component rendering - All visual states and variants
- Form integration - ControlValueAccessor implementation
- Keyboard navigation - Arrow keys, Enter, Escape, Tab
- Validation - Error states and validation display
- Accessibility - ARIA attributes and screen reader support
- User interactions - Click, keyboard, focus events
Architecture Notes
Design Patterns
1. ControlValueAccessor Pattern
All form controls implement the CVA interface for seamless Angular Forms integration.
Benefits:
- Works with both reactive and template-driven forms
- Automatic validation integration
- Consistent API across all controls
2. Signal-Based Reactivity
Components use Angular signals for reactive state management:
appearance = input<CheckboxAppearance>(CheckboxAppearance.Checkbox);
appearanceClass = computed(() =>
this.appearance() === CheckboxAppearance.Bullet
? 'ui-checkbox__bullet'
: 'ui-checkbox__checkbox'
);
3. CDK Integration
Leverages Angular CDK for complex interactions:
- Overlay - Dropdown positioning (CdkConnectedOverlay)
- A11y - Keyboard navigation (ActiveDescendantKeyManager)
- Listbox - Accessible list selection (CdkListbox)
Performance Considerations
- OnPush Change Detection - All components use OnPush strategy
- Signal Reactivity - Computed signals prevent unnecessary re-renders
- KeyManager - Efficient keyboard navigation without DOM queries
Dependencies
Required Libraries
@angular/core- Angular framework@angular/forms- Forms module@angular/cdk/a11y- Accessibility utilities@angular/cdk/listbox- Listbox primitive@angular/cdk/overlay- Overlay positioning@angular/cdk/collections- SelectionModel@ng-icons/core- Icon system@isa/icons- ISA icon librarylodash- Utility functions (isEqual)
Path Alias
Import from: @isa/ui/input-controls
License
Internal ISA Frontend library - not for external distribution.