input group

Add addons, buttons, and helper content to inputs.

PreviousNext
12 results
import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideSearch } from '@ng-icons/lucide';

import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

@Component({
  selector: 'z-demo-input-group-default',
  imports: [ZardInputComponent, NgIcon, ...ZardInputGroupImports],
  template: `
    <z-input-group class="min-w-xs">
      <input z-input placeholder="Search..." />
      <z-input-group-addon>
        <ng-icon name="lucideSearch" />
      </z-input-group-addon>
      <z-input-group-addon zAlign="inline-end">12 results</z-input-group-addon>
    </z-input-group>
  `,
  viewProviders: [provideIcons({ lucideSearch })],
})
export class ZardDemoInputGroupDefaultComponent {}

Installation

Copy
npx zard-cli@latest add input-group

Usage

import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
Copy
<z-input-group>
  <z-input-group-addon>
    <span z-input-group-text>https://</span>
  </z-input-group-addon>
  <input z-input placeholder="example.com" />
</z-input-group>
Copy

Examples

inline start

Use zAlign="inline-start" to position the addon at the start of the input. This is the default.

Icon positioned at the start.

import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideSearch } from '@ng-icons/lucide';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

@Component({
  selector: 'z-demo-input-group-inline-start',
  imports: [ZardInputComponent, NgIcon, ...ZardInputGroupImports, ...ZardFieldImports],
  template: `
    <div z-field class="min-w-sm">
      <label z-field-label for="inline-start-input">Input</label>
      <z-input-group>
        <input z-input id="inline-start-input" placeholder="Search..." />
        <z-input-group-addon zAlign="inline-start">
          <ng-icon name="lucideSearch" class="text-muted-foreground" />
        </z-input-group-addon>
      </z-input-group>
      <p z-field-description>Icon positioned at the start.</p>
    </div>
  `,
  viewProviders: [provideIcons({ lucideSearch })],
})
export class ZardDemoInputGroupInlineStartComponent {}

inline end

Use zAlign="inline-end" to position the addon at the end of the input.

Icon positioned at the end.

import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideEyeOff } from '@ng-icons/lucide';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

@Component({
  selector: 'z-demo-input-group-inline-end',
  imports: [ZardInputComponent, NgIcon, ...ZardInputGroupImports, ...ZardFieldImports],
  template: `
    <div z-field class="min-w-sm">
      <label z-field-label for="inline-end-input">Input</label>
      <z-input-group>
        <input z-input id="inline-end-input" type="password" placeholder="Enter password" />
        <z-input-group-addon zAlign="inline-end">
          <ng-icon name="lucideEyeOff" />
        </z-input-group-addon>
      </z-input-group>
      <p z-field-description>Icon positioned at the end.</p>
    </div>
  `,
  viewProviders: [provideIcons({ lucideEyeOff })],
})
export class ZardDemoInputGroupInlineEndComponent {}

block start

Use zAlign="block-start" to position the addon above the input.

Full Name

Header positioned above the input.

script.js

Header positioned above the textarea.

import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCopy, lucideFileCode } from '@ng-icons/lucide';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardTextareaComponent } from '@/shared/components/textarea/textarea.component';

@Component({
  selector: 'z-demo-input-group-block-start',
  imports: [ZardInputComponent, ZardTextareaComponent, NgIcon, ...ZardInputGroupImports, ...ZardFieldImports],
  template: `
    <div z-field-group class="min-w-sm">
      <div z-field>
        <label z-field-label for="block-start-input">Input</label>
        <z-input-group class="h-auto">
          <input z-input id="block-start-input" placeholder="Enter your name" />
          <z-input-group-addon zAlign="block-start">
            <span z-input-group-text>Full Name</span>
          </z-input-group-addon>
        </z-input-group>
        <p z-field-description>Header positioned above the input.</p>
      </div>
      <div z-field>
        <label z-field-label for="block-start-textarea">Textarea</label>
        <z-input-group>
          <textarea
            z-textarea
            id="block-start-textarea"
            placeholder="console.log('Hello, world!');"
            class="font-mono text-sm"
          ></textarea>
          <z-input-group-addon zAlign="block-start">
            <ng-icon name="lucideFileCode" class="text-muted-foreground" />
            <span z-input-group-text class="font-mono">script.js</span>
            <button type="button" z-input-group-button zSize="icon-xs" class="ml-auto">
              <ng-icon name="lucideCopy" />
              <span class="sr-only">Copy</span>
            </button>
          </z-input-group-addon>
        </z-input-group>
        <p z-field-description>Header positioned above the textarea.</p>
      </div>
    </div>
  `,
  viewProviders: [provideIcons({ lucideCopy, lucideFileCode })],
})
export class ZardDemoInputGroupBlockStartComponent {}

block end

Use zAlign="block-end" to position the addon below the input.

USD

Footer positioned below the input.

0/280

Footer positioned below the textarea.

import { Component } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardTextareaComponent } from '@/shared/components/textarea/textarea.component';

@Component({
  selector: 'z-demo-input-group-block-end',
  imports: [ZardInputComponent, ZardTextareaComponent, ...ZardInputGroupImports, ...ZardFieldImports],
  template: `
    <div z-field-group class="min-w-sm">
      <div z-field>
        <label z-field-label for="block-end-input">Input</label>
        <z-input-group class="h-auto">
          <input z-input id="block-end-input" placeholder="Enter amount" />
          <z-input-group-addon zAlign="block-end">
            <span z-input-group-text>USD</span>
          </z-input-group-addon>
        </z-input-group>
        <p z-field-description>Footer positioned below the input.</p>
      </div>
      <div z-field>
        <label z-field-label for="block-end-textarea">Textarea</label>
        <z-input-group>
          <textarea z-textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
          <z-input-group-addon zAlign="block-end">
            <span z-input-group-text>0/280</span>
            <button z-input-group-button zVariant="default" zSize="sm" class="ml-auto">Post</button>
          </z-input-group-addon>
        </z-input-group>
        <p z-field-description>Footer positioned below the textarea.</p>
      </div>
    </div>
  `,
})
export class ZardDemoInputGroupBlockEndComponent {}

icon

import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCheck, lucideCreditCard, lucideInfo, lucideMail, lucideSearch, lucideStar } from '@ng-icons/lucide';

import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

@Component({
  selector: 'z-demo-input-group-icon',
  imports: [ZardInputComponent, NgIcon, ...ZardInputGroupImports],
  template: `
    <div class="grid w-full min-w-sm gap-6">
      <z-input-group>
        <input z-input placeholder="Search..." />
        <z-input-group-addon>
          <ng-icon name="lucideSearch" />
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input type="email" placeholder="Enter your email" />
        <z-input-group-addon>
          <ng-icon name="lucideMail" />
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input placeholder="Card number" />
        <z-input-group-addon>
          <ng-icon name="lucideCreditCard" />
        </z-input-group-addon>
        <z-input-group-addon zAlign="inline-end">
          <ng-icon name="lucideCheck" />
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input placeholder="Card number" />
        <z-input-group-addon zAlign="inline-end">
          <ng-icon name="lucideStar" />
          <ng-icon name="lucideInfo" />
        </z-input-group-addon>
      </z-input-group>
    </div>
  `,
  viewProviders: [provideIcons({ lucideSearch, lucideMail, lucideCreditCard, lucideCheck, lucideStar, lucideInfo })],
})
export class ZardDemoInputGroupIconComponent {}

text

$USDhttps://.com@company.com120 characters left
import { Component } from '@angular/core';

import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardTextareaComponent } from '@/shared/components/textarea/textarea.component';

@Component({
  selector: 'z-demo-input-group-text',
  imports: [ZardInputComponent, ZardTextareaComponent, ...ZardInputGroupImports],
  template: `
    <div class="grid w-full min-w-sm gap-6">
      <z-input-group>
        <z-input-group-addon><span z-input-group-text>$</span></z-input-group-addon>
        <input z-input placeholder="0.00" />
        <z-input-group-addon zAlign="inline-end"><span z-input-group-text>USD</span></z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <z-input-group-addon><span z-input-group-text>https://</span></z-input-group-addon>
        <input z-input placeholder="example.com" class="pl-0.5!" />
        <z-input-group-addon zAlign="inline-end"><span z-input-group-text>.com</span></z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input placeholder="Enter your username" />
        <z-input-group-addon zAlign="inline-end">
          <span z-input-group-text>&#64;company.com</span>
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <textarea z-textarea placeholder="Enter your message"></textarea>
        <z-input-group-addon zAlign="block-end">
          <span z-input-group-text class="text-muted-foreground text-xs">120 characters left</span>
        </z-input-group-addon>
      </z-input-group>
    </div>
  `,
})
export class ZardDemoInputGroupTextComponent {}

button

https://
import { Component, signal } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCheck, lucideCopy, lucideInfo, lucideStar } from '@ng-icons/lucide';

import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardPopoverComponent, ZardPopoverDirective } from '@/shared/components/popover/popover.component';

@Component({
  selector: 'z-demo-input-group-button',
  imports: [ZardInputComponent, ZardPopoverComponent, ZardPopoverDirective, NgIcon, ...ZardInputGroupImports],
  template: `
    <div class="grid w-full min-w-sm gap-6">
      <z-input-group>
        <input z-input placeholder="https://x.com/shadcn" readonly />
        <z-input-group-addon zAlign="inline-end">
          <button
            type="button"
            z-input-group-button
            zSize="icon-xs"
            aria-label="Copy"
            title="Copy"
            (click)="copy('https://x.com/shadcn')"
          >
            <ng-icon [name]="isCopied() ? 'lucideCheck' : 'lucideCopy'" />
          </button>
        </z-input-group-addon>
      </z-input-group>

      <z-input-group class="[--radius:9999px]">
        <z-input-group-addon>
          <button
            type="button"
            z-input-group-button
            zVariant="secondary"
            zSize="icon-xs"
            zPopover
            [zContent]="popoverContent"
          >
            <ng-icon name="lucideInfo" />
          </button>
        </z-input-group-addon>
        <z-input-group-addon class="text-muted-foreground pl-1.5">https://</z-input-group-addon>
        <input z-input id="input-secure-19" />
        <z-input-group-addon zAlign="inline-end">
          <button type="button" z-input-group-button zSize="icon-xs" (click)="toggleFavorite()">
            <ng-icon
              name="lucideStar"
              [attr.data-favorite]="isFavorite()"
              class="[&_path]:transition-colors data-[favorite=true]:[&_path]:fill-blue-600 data-[favorite=true]:[&_path]:stroke-blue-600"
            />
          </button>
        </z-input-group-addon>
      </z-input-group>

      <z-input-group>
        <input z-input placeholder="Type to search..." />
        <z-input-group-addon zAlign="inline-end">
          <button type="button" z-input-group-button zVariant="secondary">Search</button>
        </z-input-group-addon>
      </z-input-group>

      <ng-template #popoverContent>
        <z-popover class="flex flex-col gap-1 rounded-xl text-sm">
          <p class="font-medium">Your connection is not secure.</p>
          <p>You should not enter any sensitive information on this site.</p>
        </z-popover>
      </ng-template>
    </div>
  `,
  viewProviders: [provideIcons({ lucideCopy, lucideCheck, lucideInfo, lucideStar })],
})
export class ZardDemoInputGroupButtonComponent {
  protected readonly isCopied = signal(false);
  protected readonly isFavorite = signal(false);

  protected copy(text: string): void {
    navigator.clipboard?.writeText(text);
    this.isCopied.set(true);
    setTimeout(() => this.isCopied.set(false), 2000);
  }

  protected toggleFavorite(): void {
    this.isFavorite.update(v => !v);
  }
}

kbd

⌘K
import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideSearch } from '@ng-icons/lucide';

import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardKbdComponent } from '@/shared/components/kbd/kbd.component';

@Component({
  selector: 'z-demo-input-group-kbd',
  imports: [ZardInputComponent, ZardKbdComponent, NgIcon, ...ZardInputGroupImports],
  template: `
    <z-input-group class="min-w-sm">
      <input z-input placeholder="Search..." />
      <z-input-group-addon>
        <ng-icon name="lucideSearch" class="text-muted-foreground" />
      </z-input-group-addon>
      <z-input-group-addon zAlign="inline-end">
        <z-kbd>⌘K</z-kbd>
      </z-input-group-addon>
    </z-input-group>
  `,
  viewProviders: [provideIcons({ lucideSearch })],
})
export class ZardDemoInputGroupKbdComponent {}

dropdown

import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideChevronDown, lucideEllipsis } from '@ng-icons/lucide';

import { ZardDropdownImports } from '@/shared/components/dropdown';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

@Component({
  selector: 'z-demo-input-group-dropdown',
  imports: [ZardInputComponent, NgIcon, ZardDropdownImports, ...ZardInputGroupImports],
  template: `
    <div class="grid w-full min-w-sm gap-4">
      <z-input-group>
        <input z-input placeholder="Enter file name" />
        <z-input-group-addon zAlign="inline-end">
          <button
            type="button"
            z-input-group-button
            zSize="icon-xs"
            aria-label="More"
            z-dropdown
            [zDropdownMenu]="moreMenu"
          >
            <ng-icon name="lucideEllipsis" />
          </button>
          <z-dropdown-menu-content #moreMenu="zDropdownMenuContent">
            <z-dropdown-menu-item>Settings</z-dropdown-menu-item>
            <z-dropdown-menu-item>Copy path</z-dropdown-menu-item>
            <z-dropdown-menu-item>Open location</z-dropdown-menu-item>
          </z-dropdown-menu-content>
        </z-input-group-addon>
      </z-input-group>

      <z-input-group class="[--radius:1rem]">
        <input z-input placeholder="Enter search query" />
        <z-input-group-addon zAlign="inline-end">
          <button type="button" z-input-group-button class="pr-1.5! text-xs" z-dropdown [zDropdownMenu]="searchMenu">
            Search In...
            <ng-icon name="lucideChevronDown" class="size-3" />
          </button>
          <z-dropdown-menu-content #searchMenu="zDropdownMenuContent" class="[--radius:0.95rem]">
            <z-dropdown-menu-item>Documentation</z-dropdown-menu-item>
            <z-dropdown-menu-item>Blog Posts</z-dropdown-menu-item>
            <z-dropdown-menu-item>Changelog</z-dropdown-menu-item>
          </z-dropdown-menu-content>
        </z-input-group-addon>
      </z-input-group>
    </div>
  `,
  viewProviders: [provideIcons({ lucideEllipsis, lucideChevronDown })],
})
export class ZardDemoInputGroupDropdownComponent {}

spinner

Saving...Please wait...
import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideLoader } from '@ng-icons/lucide';

import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardSpinnerComponent } from '@/shared/components/spinner/spinner.component';

@Component({
  selector: 'z-demo-input-group-spinner',
  imports: [ZardInputComponent, ZardSpinnerComponent, NgIcon, ...ZardInputGroupImports],
  template: `
    <div class="grid w-full min-w-sm gap-4">
      <z-input-group>
        <input z-input placeholder="Searching..." />
        <z-input-group-addon zAlign="inline-end">
          <z-spinner />
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input placeholder="Processing..." />
        <z-input-group-addon>
          <z-spinner />
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input placeholder="Saving changes..." />
        <z-input-group-addon zAlign="inline-end">
          <span z-input-group-text>Saving...</span>
          <z-spinner />
        </z-input-group-addon>
      </z-input-group>
      <z-input-group>
        <input z-input placeholder="Refreshing data..." />
        <z-input-group-addon>
          <ng-icon name="lucideLoader" class="animate-spin" />
        </z-input-group-addon>
        <z-input-group-addon zAlign="inline-end">
          <span z-input-group-text class="text-muted-foreground">Please wait...</span>
        </z-input-group-addon>
      </z-input-group>
    </div>
  `,
  viewProviders: [provideIcons({ lucideLoader })],
})
export class ZardDemoInputGroupSpinnerComponent {}

textarea

Line 1, Column 1 script.js
import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCopy, lucideCornerDownLeft, lucideFileCode2, lucideRefreshCw } from '@ng-icons/lucide';

import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';
import { ZardTextareaComponent } from '@/shared/components/textarea/textarea.component';

@Component({
  selector: 'z-demo-input-group-textarea',
  imports: [ZardTextareaComponent, NgIcon, ...ZardInputGroupImports],
  template: `
    <div class="grid w-full min-w-md gap-4">
      <z-input-group>
        <textarea
          z-textarea
          id="textarea-code-32"
          placeholder="console.log('Hello, world!');"
          class="min-h-[200px]"
        ></textarea>
        <z-input-group-addon zAlign="block-end" class="border-t">
          <span z-input-group-text>Line 1, Column 1</span>
          <button type="button" z-input-group-button zVariant="default" zSize="sm" class="ml-auto">
            Run
            <ng-icon name="lucideCornerDownLeft" />
          </button>
        </z-input-group-addon>
        <z-input-group-addon zAlign="block-start" class="border-b">
          <span z-input-group-text class="font-mono font-medium">
            <ng-icon name="lucideFileCode2" />
            script.js
          </span>
          <button type="button" z-input-group-button zSize="icon-xs" class="ml-auto" aria-label="Refresh">
            <ng-icon name="lucideRefreshCw" />
          </button>
          <button type="button" z-input-group-button zSize="icon-xs" aria-label="Copy">
            <ng-icon name="lucideCopy" />
          </button>
        </z-input-group-addon>
      </z-input-group>
    </div>
  `,
  viewProviders: [provideIcons({ lucideFileCode2, lucideCopy, lucideCornerDownLeft, lucideRefreshCw })],
})
export class ZardDemoInputGroupTextareaComponent {}

custom

Add the data-slot="input-group-control" attribute to your custom input for automatic focus state handling.

import { Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucidePencil } from '@ng-icons/lucide';

import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

@Component({
  selector: 'z-demo-input-group-custom',
  imports: [NgIcon, ...ZardInputGroupImports],
  template: `
    <z-input-group class="w-80">
      <z-input-group-addon><ng-icon name="lucidePencil" /></z-input-group-addon>
      <input
        data-slot="input-group-control"
        type="text"
        placeholder="Custom input control..."
        class="placeholder:text-muted-foreground bg-transparent text-sm outline-none"
      />
    </z-input-group>
  `,
  viewProviders: [provideIcons({ lucidePencil })],
})
export class ZardDemoInputGroupCustomComponent {}

API Reference

z-input-groupComponent

Container that groups an input or textarea with addons (text, icons, buttons).

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''

z-input-group-addonComponent

A slot inside the group for prefix/suffix content. Clicking it focuses the inner input.

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''
[zAlign] Addon alignment 'inline-start' | 'inline-end' | 'block-start' | 'block-end' 'inline-start'

button[z-input-group-button]Component

Compact button styled to fit inside an InputGroup.

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''
[zSize] Button size 'xs' | 'sm' | 'icon-xs' | 'icon-sm' 'xs'

z-input-group-textComponent

Inline text label inside an addon (e.g. currency symbol, unit suffix).

PropertyDescriptionTypeDefault
[class] Additional CSS classes ClassValue ''
github iconwhatsapp icondiscord iconX icon

Made with in Brazil. Open source and available on GitHub .