Skip to content
Ndop UI

Input

Displays a form input field.

  • v1.0.0
  • SSR
  • Zoneless
  • 1.8 kB gzip
import { Component } from '@angular/core';
import { Input } from '@/ui/input/input';

/** A plain email input. */
@Component({
  selector: 'example-input-demo',
  imports: [Input],
  template: `
    <input
      ndInput
      type="email"
      placeholder="Email"
      aria-label="Email"
      class="max-w-sm"
    />
  `,
})
export class InputDemoExample {}

A text input collects a single line of text. ndInput styles a native input and adds no value accessor, so forms bind it directly: Signal Forms with [formField], reactive forms with formControlName, or plain attributes. It reads the bound field's state and shows the input as invalid once the field is invalid and touched, with aria-invalid and data-invalid.

Installation

npx @ndop/cli add input

Usage

import { Input } from '@/ui/input/input';
<input
  ndInput
  type="email"
  placeholder="Email"
  aria-label="Email"
  class="max-w-sm"
/>

Examples

Input signal forms

An input bound with Signal Forms. It turns invalid once the field is touched and fails validation, and the label follows it.

We'll send the invite here.

import { Component, signal } from '@angular/core';
import { email, form, FormField, required } from '@angular/forms/signals';
import { Input } from '@/ui/input/input';
import { Label } from '@/ui/label/label';

/**
 * An input bound with Signal Forms. It turns invalid once the field is
 * touched and fails validation, and the label follows it.
 */
@Component({
  selector: 'example-input-signal-forms',
  imports: [Input, Label, FormField],
  template: `
    <div class="grid w-full max-w-sm gap-3">
      <label ndLabel for="input-signal-forms-email">Work email</label>
      <input
        ndInput
        id="input-signal-forms-email"
        type="email"
        placeholder="you@company.com"
        aria-describedby="input-signal-forms-hint"
        [formField]="account.email"
      />
      <p id="input-signal-forms-hint" class="text-muted-foreground text-sm">
        @if (account.email().touched() && account.email().invalid()) {
          Enter a valid email address.
        } @else {
          We'll send the invite here.
        }
      </p>
    </div>
  `,
})
export class InputSignalFormsExample {
  private readonly model = signal({ email: '' });

  protected readonly account = form(this.model, (path) => {
    required(path.email);
    email(path.email);
  });
}

Input with button

An input and a button on one row, as in a newsletter sign-up.

import { Component } from '@angular/core';
import { Button } from '@/ui/button/button';
import { Input } from '@/ui/input/input';

/** An input and a button on one row, as in a newsletter sign-up. */
@Component({
  selector: 'example-input-with-button',
  imports: [Input, Button],
  template: `
    <form
      class="flex w-full max-w-sm items-center gap-2"
      (submit)="$event.preventDefault()"
    >
      <input ndInput type="email" placeholder="Email" aria-label="Email" />
      <button ndButton type="submit" variant="outline">Subscribe</button>
    </form>
  `,
})
export class InputWithButtonExample {}

Input disabled

A disabled input with its label.

import { Component } from '@angular/core';
import { Input } from '@/ui/input/input';
import { Label } from '@/ui/label/label';

/** A disabled input; its label dims with it. */
@Component({
  selector: 'example-input-disabled',
  imports: [Input, Label],
  template: `
    <div class="grid w-full max-w-sm gap-3">
      <label ndLabel for="input-disabled-email">Email</label>
      <input
        ndInput
        id="input-disabled-email"
        type="email"
        value="team@example.com"
        disabled
      />
    </div>
  `,
})
export class InputDisabledExample {}

Accessibility

Focus

  • Unchanged from the native input. Focusing it and leaving it marks the bound field touched, through the forms library.

Right-to-left

Text direction follows the document or the element's dir; Ndop changes nothing.

API reference

Input

input[ndInput]role native

Styled text input. Forms bind the native input directly; the @ndop/primitives/input behavior reflects their state as aria-invalid once the field is touched.

Manages aria-invalid, aria-describedby.

Input binding Type Default
[class]
Extra classes, merged last so they win.
string''
[id]
Element id; also the key a label[ndLabel] uses to find this input.
string | undefined—
[invalid]
Marks the input invalid when no forms library is bound; with one, the field's state decides.
booleanfalse
Data attribute Values
data-slotinput
data-disabled —
data-invalid —

Agent notes

Do

  • Pair every input with an ndLabel whose for matches the input's id.
  • Bind it with Signal Forms ([formField]) or reactive forms, and let the field's validity drive the invalid state.
  • Set the right type (email, password, search, tel) so devices show the matching keyboard and browsers can autofill.

Don't

  • Don't set invalid while a forms library is bound; the field's state decides.
  • Don't rely on color alone for errors; describe them in text linked with aria-describedby.