AvesraAvesrabeta

Input Group

Group related input controls with prefix and suffix elements for enhanced form fields. Apply av-input-group to a container <div>.

Import

import { AvInputGroupImports } from '@avesra/angular';

Compose with av-label, av-description, and av-field-error. For form-level validation and submission, see Form.

Usage

import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-basic-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-email">Email address</label>
    <div av-input-group>
      <div av-input-group-prefix>
        <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
      </div>
      <input
        av-input-group-input
        id="ig-email"
        type="email"
        placeholder="name&#64;email.com"
      />
    </div>`,
})
export class InputGroupBasicDemo {}

Anatomy

<label av-label for="field">Label</label>
<div av-input-group>
  <div av-input-group-prefix><!-- icon or text --></div>
  <input av-input-group-input id="field" type="text" />
  <!-- Or use textarea[av-input-group-textarea] for multiline -->
  <div av-input-group-suffix><!-- icon, text, or actions --></div>
</div>
<p av-description>Helper text</p>
<p av-field-error [visible]="false">Error message</p>

Input Group wraps an input field with optional prefix and suffix elements, creating a visually cohesive group. Add icons, text, buttons, or other elements before or after the control. Use av-input-group-input on a native <input> for single-line fields, or av-input-group-textarea on a native <textarea> for multiline text.

Variants

The Input Group component supports two visual variants:

  • primary (default) — Standard styling with shadow, suitable for most use cases
  • secondary — Lower emphasis variant without shadow, suitable for use in Surface components
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-variants-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-4' },
  template: `<div class="flex flex-col gap-1">
      <label av-label for="ig-primary">Primary variant</label>
      <div av-input-group variant="primary">
        <div av-input-group-prefix>
          <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
        </div>
        <input
          av-input-group-input
          id="ig-primary"
          placeholder="name&#64;email.com"
        />
      </div>
    </div>

    <div class="flex flex-col gap-1">
      <label av-label for="ig-secondary">Secondary variant</label>
      <div av-input-group variant="secondary">
        <div av-input-group-prefix>
          <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
        </div>
        <input
          av-input-group-input
          id="ig-secondary"
          placeholder="name&#64;email.com"
        />
      </div>
    </div>`,
})
export class InputGroupVariantsDemo {}

In Surface

When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.

We'll never share this with anyone else

import { Component } from '@angular/core';
import {
  AvDescriptionComponent,
  AvInputGroupImports,
  AvLabelComponent,
  AvSurfaceComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-on-surface-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvDescriptionComponent,
    AvSurfaceComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full items-center justify-center' },
  template: `<div av-surface class="rounded-2xl p-6">
      <div class="flex w-full max-w-72 flex-col gap-1">
        <label av-label for="ig-on-surface">Email address</label>
        <div av-input-group variant="secondary">
          <div av-input-group-prefix>
            <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
          </div>
          <input
            av-input-group-input
            id="ig-on-surface"
            type="email"
            placeholder="name&#64;email.com"
          />
        </div>
        <p av-description>We'll never share this with anyone else</p>
      </div>
    </div>`,
})
export class InputGroupOnSurfaceDemo {}

Loading State

Show a loading spinner in the suffix to indicate processing.

import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvSpinnerComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-with-loading-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvSpinnerComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<div av-input-group>
      <input av-input-group-input value="Sending..." aria-label="Status" />
      <div av-input-group-suffix>
        <span av-spinner size="sm" color="current" aria-label="Loading"></span>
      </div>
    </div>`,
})
export class InputGroupWithLoadingSuffixDemo {}

Required Field

Mark the field as required with required on the label and the native control. Pair with av-description for helper text.

$
USD

What customers would pay

import { Component } from '@angular/core';
import {
  AvDescriptionComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-required-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvDescriptionComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-4' },
  template: `<div class="flex flex-col gap-1">
      <label av-label for="ig-required-email" required>Email address</label>
      <div av-input-group>
        <div av-input-group-prefix>
          <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
        </div>
        <input
          av-input-group-input
          id="ig-required-email"
          type="email"
          placeholder="name&#64;email.com"
          required
        />
      </div>
    </div>

    <div class="flex flex-col gap-1">
      <label av-label for="ig-required-price" required>Set a price</label>
      <div av-input-group>
        <div av-input-group-prefix>$</div>
        <input
          av-input-group-input
          id="ig-required-price"
          type="number"
          placeholder="0"
          required
        />
        <div av-input-group-suffix>USD</div>
      </div>
      <p av-description>What customers would pay</p>
    </div>`,
})
export class InputGroupRequiredDemo {}

Disabled State

Set disabled on the group to disable the control and style all parts together. The inner input inherits disabled state from context.

$
USD
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-disabled-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-4' },
  template: `<div class="flex flex-col gap-1">
      <label av-label for="ig-disabled-email" disabled>Email address</label>
      <div av-input-group disabled>
        <div av-input-group-prefix>
          <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
        </div>
        <input
          av-input-group-input
          id="ig-disabled-email"
          type="email"
          value="name&#64;email.com"
        />
      </div>
    </div>

    <div class="flex flex-col gap-1">
      <label av-label for="ig-disabled-price" disabled>Set a price</label>
      <div av-input-group disabled>
        <div av-input-group-prefix>$</div>
        <input av-input-group-input id="ig-disabled-price" type="number" value="10" />
        <div av-input-group-suffix>USD</div>
      </div>
    </div>`,
})
export class InputGroupDisabledDemo {}

Full Width

Set full-width on the group to expand to the container width.

import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-full-width-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-sm flex-col gap-4' },
  template: `<div class="flex w-full flex-col gap-1">
      <label av-label for="ig-fw-email">Email address</label>
      <div av-input-group full-width>
        <div av-input-group-prefix>
          <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
        </div>
        <input
          av-input-group-input
          id="ig-fw-email"
          type="email"
          placeholder="name&#64;email.com"
        />
      </div>
    </div>

    <div class="flex w-full flex-col gap-1">
      <label av-label for="ig-fw-password">Password</label>
      <div av-input-group full-width>
        <input
          av-input-group-input
          id="ig-fw-password"
          type="password"
          placeholder="Enter password"
        />
        <div av-input-group-suffix>
          <app-icon icon="solar:eye-linear" size="16" class="text-muted" />
        </div>
      </div>
    </div>`,
})
export class InputGroupFullWidthDemo {}

Text Prefix

Use text as a prefix, such as currency symbols or protocol prefixes.

https://
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-with-text-prefix-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-text-prefix">Website</label>
    <div av-input-group>
      <div av-input-group-prefix>https://</div>
      <input av-input-group-input id="ig-text-prefix" value="avesraui.com" />
    </div>`,
})
export class InputGroupWithTextPrefixDemo {}

Text Suffix

Use text as a suffix, such as domain extensions or units.

.com
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-with-text-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-text-suffix">Website</label>
    <div av-input-group>
      <input av-input-group-input id="ig-text-suffix" value="avesraui" />
      <div av-input-group-suffix>.com</div>
    </div>`,
})
export class InputGroupWithTextSuffixDemo {}

Icon Prefix and Text Suffix

Combine an icon prefix with a text suffix.

.com
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-with-icon-prefix-and-text-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-icon-text">Website</label>
    <div av-input-group>
      <div av-input-group-prefix>
        <app-icon icon="solar:global-linear" size="16" class="text-muted" />
      </div>
      <input av-input-group-input id="ig-icon-text" value="avesraui" />
      <div av-input-group-suffix>.com</div>
    </div>`,
})
export class InputGroupWithIconPrefixAndTextSuffixDemo {}

Copy Button Suffix

Add an interactive button in the suffix, such as a copy button.

import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-with-copy-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvButtonComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-copy">Website</label>
    <div av-input-group>
      <input av-input-group-input id="ig-copy" value="avesraui.com" />
      <div av-input-group-suffix class="pr-0">
        <button
          av-button
          type="button"
          size="sm"
          variant="ghost"
          icon-only
          aria-label="Copy"
          (click)="copyWebsite()"
        >
          <app-icon icon="solar:copy-linear" size="16" />
        </button>
      </div>
    </div>`,
})
export class InputGroupWithCopySuffixDemo {
  copyWebsite(): void {
    void navigator.clipboard?.writeText('avesraui.com');
  }
}

Icon Prefix and Copy Button

Combine an icon prefix with an interactive button suffix.

import { Component } from '@angular/core';
import {
  AvButtonComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-with-icon-prefix-and-copy-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvButtonComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-icon-copy">Website</label>
    <div av-input-group>
      <div av-input-group-prefix>
        <app-icon icon="solar:global-linear" size="16" class="text-muted" />
      </div>
      <input av-input-group-input id="ig-icon-copy" value="avesraui.com" />
      <div av-input-group-suffix class="pr-0">
        <button
          av-button
          type="button"
          size="sm"
          variant="ghost"
          icon-only
          aria-label="Copy"
          (click)="copyWebsite()"
        >
          <app-icon icon="solar:copy-linear" size="16" />
        </button>
      </div>
    </div>`,
})
export class InputGroupWithIconPrefixAndCopySuffixDemo {
  copyWebsite(): void {
    void navigator.clipboard?.writeText('avesraui.com');
  }
}

Password Toggle

Use a button in the suffix to toggle password visibility.

import { Component, signal } from '@angular/core';
import {
  AvButtonComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-password-with-toggle-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvButtonComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-password">Password</label>
    <div av-input-group>
      <input
        av-input-group-input
        id="ig-password"
        [type]="passwordVisible() ? 'text' : 'password'"
        [value]="passwordVisible() ? '87$2h.3diua' : '••••••••'"
        readonly
      />
      <div av-input-group-suffix class="pr-0">
        <button
          av-button
          type="button"
          size="sm"
          variant="ghost"
          icon-only
          [attr.aria-label]="passwordVisible() ? 'Hide password' : 'Show password'"
          (click)="togglePasswordVisibility()"
        >
          @if (passwordVisible()) {
            <app-icon icon="solar:eye-linear" size="16" />
          } @else {
            <app-icon icon="solar:eye-closed-linear" size="16" />
          }
        </button>
      </div>
    </div>`,
})
export class InputGroupPasswordWithToggleDemo {
  readonly passwordVisible = signal(false);

  togglePasswordVisibility(): void {
    this.passwordVisible.update((value) => !value);
  }
}

Keyboard Shortcut

Display keyboard shortcuts using the Kbd component.

⌘K
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvKbdImports,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-with-keyboard-shortcut-demo',
  imports: [
    AvInputGroupImports,
    AvKbdImports,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<div av-input-group>
      <input av-input-group-input placeholder="Command" aria-label="Command" />
      <div av-input-group-suffix class="pr-2">
        <kbd av-kbd>
          <abbr av-kbd-abbr key-value="command"></abbr>
          <span av-kbd-content>K</span>
        </kbd>
      </div>
    </div>`,
})
export class InputGroupWithKeyboardShortcutDemo {}

Badge Suffix

Add a badge or chip in the suffix to show status or labels.

Pro
import { Component } from '@angular/core';
import {
  AvChipImports,
  AvInputGroupImports,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-with-badge-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvChipImports,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<div av-input-group>
      <input
        av-input-group-input
        placeholder="Email address"
        aria-label="Email address"
      />
      <div av-input-group-suffix class="pr-2">
        <span av-chip color="accent" size="md" variant="soft" label="Pro"></span>
      </div>
    </div>`,
})
export class InputGroupWithBadgeSuffixDemo {}

Validation

Set invalid on the group (and optionally the label) to reflect an invalid state. Show messages with av-field-error.

$
USD
import { Component } from '@angular/core';
import {
  AvFieldErrorComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-invalid-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvFieldErrorComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-4' },
  template: `<div class="flex flex-col gap-1">
      <label av-label for="ig-invalid-email" required invalid>Email address</label>
      <div av-input-group invalid>
        <div av-input-group-prefix>
          <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
        </div>
        <input
          av-input-group-input
          id="ig-invalid-email"
          type="email"
          placeholder="name&#64;email.com"
          required
        />
      </div>
      <p av-field-error [visible]="true">Please enter a valid email address</p>
    </div>

    <div class="flex flex-col gap-1">
      <label av-label for="ig-invalid-price" required invalid>Set a price</label>
      <div av-input-group invalid>
        <div av-input-group-prefix>$</div>
        <input
          av-input-group-input
          id="ig-invalid-price"
          type="number"
          placeholder="0"
          required
        />
        <div av-input-group-suffix>USD</div>
      </div>
      <p av-field-error [visible]="true">Price must be greater than 0</p>
    </div>`,
})
export class InputGroupInvalidDemo {}

With Prefix Icon

Add an icon before the input field.

We'll never share this with anyone else

import { Component } from '@angular/core';
import {
  AvDescriptionComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-with-prefix-icon-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvDescriptionComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-prefix-icon">Email address</label>
    <div av-input-group>
      <div av-input-group-prefix>
        <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
      </div>
      <input
        av-input-group-input
        id="ig-prefix-icon"
        type="email"
        placeholder="name&#64;email.com"
      />
    </div>
    <p av-description>We'll never share this with anyone else</p>`,
})
export class InputGroupWithPrefixIconDemo {}

With Suffix Icon

Add an icon after the input field.

We don't send spam

import { Component } from '@angular/core';
import {
  AvDescriptionComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-with-suffix-icon-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvDescriptionComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-suffix-icon">Email address</label>
    <div av-input-group>
      <input
        av-input-group-input
        id="ig-suffix-icon"
        type="email"
        placeholder="name&#64;email.com"
      />
      <div av-input-group-suffix>
        <app-icon icon="solar:letter-linear" size="16" class="text-muted" />
      </div>
    </div>
    <p av-description>We don't send spam</p>`,
})
export class InputGroupWithSuffixIconDemo {}

With Prefix and Suffix

Combine both prefix and suffix elements.

$
USD

What customers would pay

import { Component } from '@angular/core';
import {
  AvDescriptionComponent,
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-with-prefix-and-suffix-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
    AvDescriptionComponent,
  ],
  host: { class: 'flex w-full max-w-72 flex-col gap-1' },
  template: `<label av-label for="ig-price">Set a price</label>
    <div av-input-group>
      <div av-input-group-prefix>$</div>
      <input av-input-group-input id="ig-price" type="number" value="10" />
      <div av-input-group-suffix>USD</div>
    </div>
    <p av-description>What customers would pay</p>`,
})
export class InputGroupWithPrefixAndSuffixDemo {}

With TextArea

Use textarea[av-input-group-textarea] for multiline text inputs with prefix and suffix elements. When a textarea is present, the container automatically adjusts its height and aligns prefix/suffix elements to the top.

import { Component, signal } from '@angular/core';
import {
  AvButtonComponent,
  AvInputGroupImports,
  AvSpinnerComponent,
} from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-input-group-with-textarea-demo',
  imports: [
    AvInputGroupImports,
    AvButtonComponent,
    AvSpinnerComponent,
    AppIconComponent,
  ],
  host: { class: 'flex w-full max-w-lg flex-col' },
  template: `<div
      av-input-group
      full-width
      class="flex flex-col gap-2 rounded-3xl py-2"
      aria-label="Prompt input"
    >
      <div av-input-group-prefix class="px-3 py-0">
        <button av-button type="button" size="sm" variant="outline">
          <app-icon icon="solar:mention-circle-linear" size="16" />
          Add Context
        </button>
      </div>
      <textarea
        av-input-group-textarea
        class="w-full resize-none px-3.5 py-0"
        placeholder="Assign tasks or ask anything..."
        rows="5"
        [value]="promptValue()"
        (input)="onPromptInput($event)"
      ></textarea>
      <div av-input-group-suffix class="flex w-full items-center gap-1.5 px-3 py-0">
        <button av-button type="button" size="sm" variant="tertiary" icon-only aria-label="Attach file">
          <app-icon icon="solar:add-circle-linear" size="16" />
        </button>
        <div class="ml-auto flex items-center gap-1.5">
          <button av-button type="button" size="sm" variant="ghost" icon-only aria-label="Voice input">
            <app-icon icon="solar:microphone-linear" size="16" />
          </button>
          <button
            av-button
            type="button"
            size="sm"
            icon-only
            aria-label="Send prompt"
            [disabled]="!promptValue().trim() || isSubmitting()"
            (click)="handleSubmit()"
          >
            @if (isSubmitting()) {
              <span av-spinner size="sm" color="current"></span>
            } @else {
              <app-icon icon="solar:arrow-up-linear" size="16" />
            }
          </button>
        </div>
      </div>
    </div>`,
})
export class InputGroupWithTextareaDemo {
  readonly promptValue = signal('');
  readonly isSubmitting = signal(false);

  onPromptInput(event: Event): void {
    this.promptValue.set((event.target as HTMLTextAreaElement).value);
  }

  handleSubmit(): void {
    if (!this.promptValue().trim()) {
      return;
    }

    this.isSubmitting.set(true);
    setTimeout(() => {
      this.isSubmitting.set(false);
      this.promptValue.set('');
    }, 1000);
  }
}

Styling

Passing Tailwind CSS classes

Pass utility classes on the group host or on prefix, input, and suffix parts.

https://
.com
import { Component } from '@angular/core';
import {
  AvInputGroupImports,
  AvLabelComponent,
} from '@avesra/angular';

@Component({
  selector: 'app-input-group-custom-styling-demo',
  imports: [
    AvInputGroupImports,
    AvLabelComponent,
  ],
  host: { class: 'flex w-full max-w-80 flex-col gap-1' },
  template: `<label av-label for="ig-custom">Website</label>
    <div av-input-group class="rounded-xl border-2 border-accent">
      <div av-input-group-prefix class="bg-accent/10 text-accent">https://</div>
      <input av-input-group-input id="ig-custom" class="font-medium" />
      <div av-input-group-suffix class="bg-accent/10 text-accent">.com</div>
    </div>`,
})
export class InputGroupCustomStylingDemo {}

Customizing the component classes

Input Group uses BEM CSS classes that can be customized. Override the component classes with @layer components to match your design system.

@layer components {
  .av-input-group {
    @apply bg-field text-field-foreground shadow-field rounded-field inline-flex min-h-9 items-center overflow-hidden border text-sm outline-none;
  }

  .av-input-group__input {
    @apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
  }

  .av-input-group__prefix {
    @apply text-field-placeholder flex h-full items-center justify-center bg-transparent px-3;
  }

  .av-input-group__suffix {
    @apply text-field-placeholder flex h-full items-center justify-center bg-transparent px-3;
  }

  .av-input-group--secondary {
    @apply shadow-none;
    background-color: var(--color-default);
  }
}

CSS Classes

  • .av-input-group — Root container with border, background, and flex layout. Uses min-h-9 for flexible height and items-center by default, switching to items-start when a textarea is present.
  • .av-input-group__input — Input/textarea with transparent background and no border. Also used as the base class for textarea elements.
  • .av-input-group__prefix — Prefix container. Aligns to top when used with textarea.
  • .av-input-group__suffix — Suffix container. Aligns to top when used with textarea.
  • .av-input-group--primary — Primary variant with shadow (default)
  • .av-input-group--secondary — Secondary variant without shadow, suitable for use in surfaces
  • .av-input-group--full-width — Full-width layout

When using av-input-group-textarea, the container switches from items-center to items-start and uses height: auto. Textarea-specific styles apply via the [data-slot="input-group-textarea"] attribute selector.

Interactive States

  • Hover::hover or [data-hovered="true"]
  • Focus Within: focus styles apply when the inner input/textarea is focused (:focus-within / field focus tokens)
  • Invalid:[data-invalid="true"] (also syncs aria-invalid on the control)
  • Disabled:[data-disabled="true"] or [aria-disabled="true"]

API

Root props for div[av-input-group]. The input and textarea parts accept standard HTML attributes and implement ControlValueAccessor for [(ngModel)] or reactive forms. Prefix and suffix are content projection slots. Compose with av-label, av-description, and av-field-error for field chrome.

PropTypeDefaultDescription
variant'primary' | 'secondary''primary'Visual variant. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces.
full-widthbooleanfalseWhether the input group should take full width of its container.
disabledbooleanfalseDisables interaction for the group and its controls. Propagates to nested input/textarea via context.
invalidbooleanfalseMarks the group as invalid. Propagates `aria-invalid` to nested input/textarea via context.

Made with ❤ by SyntaxHertz. Open source and available on GitHub.