Form
Input Group
Usage#
import { WDS_INPUT_GROUP } from '@wds/angular/input-group';Examples#
Inline start#
import { Component } from '@angular/core';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
@Component({
selector: 'app-input-group-inline-start-demo',
imports: [WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputComponent],
template: `
<wds-input-group class="w-full max-w-sm">
<wds-input placeholder="Search..." aria-label="Search"></wds-input>
<wds-input-group-addon>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
</wds-input-group-addon>
</wds-input-group>
`,
})
export class InputGroupInlineStartDemo {}Inline end#
aria-pressed.
import { Component, signal } from '@angular/core';
import { WdsButtonComponent } from '@wds/angular/button';
import { WdsInputComponent } from '@wds/angular/input';
import { WDS_INPUT_GROUP } from '@wds/angular/input-group';
@Component({
selector: 'app-input-group-inline-end-demo',
imports: [...WDS_INPUT_GROUP, WdsButtonComponent, WdsInputComponent],
template: `
<wds-input-group class="w-sm max-w-full">
<wds-input [type]="visible() ? 'text' : 'password'" placeholder="Enter password" aria-label="Password" />
<wds-input-group-addon align="inline-end">
<wds-button variant="ghost" size="icon-xs" label="Show password" [attr.aria-pressed]="visible()" (click)="visible.set(!visible())">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" /><circle cx="12" cy="12" r="3" /></svg>
</wds-button>
</wds-input-group-addon>
</wds-input-group>
`,
})
export class InputGroupInlineEndDemo {
protected readonly visible = signal(false);
}Block start#
align="block-start" puts the addon above the field, e.g. a label or a file name; divider adds a line.
import { Component } from '@angular/core';
import { WdsFieldComponent, WdsFieldDescriptionComponent, WdsFieldLabelComponent } from '@wds/angular/field';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent, WdsInputGroupTextComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
import { WdsTextareaComponent } from '@wds/angular/textarea';
import { WdsButtonComponent } from '@wds/angular/button';
@Component({
selector: 'app-input-group-block-start-demo',
imports: [WdsFieldComponent, WdsFieldLabelComponent, WdsFieldDescriptionComponent, WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputGroupTextComponent, WdsInputComponent, WdsTextareaComponent, WdsButtonComponent],
template: `
<div class="flex w-full max-w-sm flex-col gap-4">
<wds-field>
<wds-input-group>
<wds-input placeholder="Jordan Lee"></wds-input>
<wds-input-group-addon align="block-start">
<wds-field-label>Full name</wds-field-label>
</wds-input-group-addon>
</wds-input-group>
<wds-field-description>The label sits inside the frame, above the field.</wds-field-description>
</wds-field>
<wds-input-group>
<wds-textarea placeholder="console.log('Hello, world!');" aria-label="Code"></wds-textarea>
<wds-input-group-addon align="block-start" divider>
<wds-input-group-text>script.js</wds-input-group-text>
<wds-button class="ms-auto" variant="ghost" size="icon-xs" label="Copy">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
</wds-button>
</wds-input-group-addon>
</wds-input-group>
</div>
`,
})
export class InputGroupBlockStartDemo {}Block end#
align="block-end" puts the addon below the field, e.g. a counter and a submit button.
import { Component, computed, signal } from '@angular/core';
import { WdsButtonComponent } from '@wds/angular/button';
import { WDS_INPUT_GROUP } from '@wds/angular/input-group';
import { WdsTextareaComponent } from '@wds/angular/textarea';
const MAX = 240;
@Component({
selector: 'app-input-group-block-end-demo',
imports: [...WDS_INPUT_GROUP, WdsButtonComponent, WdsTextareaComponent],
template: `
<wds-input-group class="w-sm max-w-full">
<wds-textarea placeholder="Write a comment..." aria-label="Comment" [maxLength]="max" [value]="comment()" (wdsInput)="comment.set($event.value)" />
<wds-input-group-addon align="block-end" divider>
<wds-input-group-text>{{ left() }} characters left</wds-input-group-text>
<wds-button class="ms-auto" size="xs" [disabled]="!comment().trim()">Post</wds-button>
</wds-input-group-addon>
</wds-input-group>
`,
})
export class InputGroupBlockEndDemo {
protected readonly max = MAX;
protected readonly comment = signal('');
protected readonly left = computed(() => MAX - this.comment().length);
}Text#
wds-input-group-text shows a prefix or a suffix.
import { Component } from '@angular/core';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent, WdsInputGroupTextComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
@Component({
selector: 'app-input-group-text-demo',
imports: [WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputGroupTextComponent, WdsInputComponent],
template: `
<div class="flex w-full max-w-sm flex-col gap-4">
<wds-input-group>
<wds-input placeholder="example" aria-label="Domain"></wds-input>
<wds-input-group-addon><wds-input-group-text>https://</wds-input-group-text></wds-input-group-addon>
<wds-input-group-addon align="inline-end"><wds-input-group-text>.com</wds-input-group-text></wds-input-group-addon>
</wds-input-group>
<wds-input-group>
<wds-input type="number" placeholder="0.00" aria-label="Amount"></wds-input>
<wds-input-group-addon><wds-input-group-text>$</wds-input-group-text></wds-input-group-addon>
<wds-input-group-addon align="inline-end"><wds-input-group-text>USD</wds-input-group-text></wds-input-group-addon>
</wds-input-group>
</div>
`,
})
export class InputGroupTextDemo {}Kbd#
import { Component } from '@angular/core';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
import { WdsKbdComponent, WdsKbdGroupComponent } from '@wds/angular/kbd';
@Component({
selector: 'app-input-group-kbd-demo',
imports: [WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputComponent, WdsKbdGroupComponent, WdsKbdComponent],
template: `
<wds-input-group class="w-full max-w-sm">
<wds-input placeholder="Search..." aria-label="Search"></wds-input>
<wds-input-group-addon>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
</wds-input-group-addon>
<wds-input-group-addon align="inline-end">
<wds-kbd-group><wds-kbd>⌘</wds-kbd><wds-kbd>K</wds-kbd></wds-kbd-group>
</wds-input-group-addon>
</wds-input-group>
`,
})
export class InputGroupKbdDemo {}Dropdown#
import { Component } from '@angular/core';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
import { WdsDropdownComponent, WdsDropdownItemComponent, WdsDropdownRadioGroupComponent } from '@wds/angular/dropdown';
import { WdsButtonComponent } from '@wds/angular/button';
@Component({
selector: 'app-input-group-dropdown-demo',
imports: [WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputComponent, WdsDropdownComponent, WdsDropdownRadioGroupComponent, WdsDropdownItemComponent, WdsButtonComponent],
template: `
<wds-input-group class="w-full max-w-sm">
<wds-input placeholder="Enter a search term..." aria-label="Search term"></wds-input>
<wds-input-group-addon align="inline-end">
<wds-dropdown align="end">
<wds-button slot="trigger" variant="ghost" size="xs">
Search in
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
</wds-button>
<wds-dropdown-radio-group value="everywhere">
<wds-dropdown-item type="radio" value="everywhere">Everywhere</wds-dropdown-item>
<wds-dropdown-item type="radio" value="documents">Documents</wds-dropdown-item>
<wds-dropdown-item type="radio" value="photos">Photos</wds-dropdown-item>
</wds-dropdown-radio-group>
</wds-dropdown>
</wds-input-group-addon>
</wds-input-group>
`,
})
export class InputGroupDropdownDemo {}Spinner#
import { Component } from '@angular/core';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent, WdsInputGroupTextComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
import { WdsSpinnerComponent } from '@wds/angular/spinner';
@Component({
selector: 'app-input-group-spinner-demo',
imports: [WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputGroupTextComponent, WdsInputComponent, WdsSpinnerComponent],
template: `
<wds-input-group class="w-full max-w-sm">
<wds-input value="@wds/core" aria-label="Package" readonly></wds-input>
<wds-input-group-addon align="inline-end">
<wds-input-group-text>Saving...</wds-input-group-text>
<wds-spinner decorative></wds-spinner>
</wds-input-group-addon>
</wds-input-group>
`,
})
export class InputGroupSpinnerDemo {}Invalid#
invalid from wds-field or on the field).
import { Component } from '@angular/core';
import { WdsFieldComponent, WdsFieldErrorComponent, WdsFieldLabelComponent } from '@wds/angular/field';
import { WdsInputGroupAddonComponent, WdsInputGroupComponent } from '@wds/angular/input-group';
import { WdsInputComponent } from '@wds/angular/input';
@Component({
selector: 'app-input-group-invalid-demo',
imports: [WdsFieldComponent, WdsFieldLabelComponent, WdsFieldErrorComponent, WdsInputGroupComponent, WdsInputGroupAddonComponent, WdsInputComponent],
template: `
<wds-field class="w-full max-w-sm" invalid>
<wds-field-label>Email</wds-field-label>
<wds-input-group>
<wds-input type="email" value="jordan@"></wds-input>
<wds-input-group-addon>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>
</wds-input-group-addon>
</wds-input-group>
<wds-field-error>Enter a valid email address.</wds-field-error>
</wds-field>
`,
})
export class InputGroupInvalidDemo {}API References#
wds-input-group#
invalid and disabled of the field; the field loses its own border.
Slots
| Slot | Description |
|---|---|
| (default) | wds-input or wds-textarea, and wds-input-group-addon elements. |
CSS parts (::part)
| Part | Description |
|---|---|
| base |
wds-input-group-addon#
wds-input-group: icon, text, button, <kbd>, menu.
A click on the addon (outside interactive elements) moves focus to the field.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| align | WdsInputGroupAddonAlign | 'inline-start' | inline-*), or above or below it (block-*). |
| divider | boolean | false | block-start / block-end). |
Slots
| Slot | Description |
|---|---|
| (default) |
CSS parts (::part)
| Part | Description |
|---|---|
| base | divider). |
wds-input-group-text#
https://, .com, a character counter).
Slots
| Slot | Description |
|---|---|
| (default) | <svg> icon. |
Accessibility#
Screen reader#
wds-input-group has role="group"; the field inside keeps its own role and name. Give the field a label (wds-field-label or aria-label): addon text such as https:// is not part of its name.
wds-button needs a label. Give icons aria-hidden="true".
invalid and disabled states of the field; the ARIA states stay on the field itself.