Input

A native input with Looma’s shared control theme. Optional segments support block selection, character filtering, automatic or typed separator advancement, and continuous authentication-code entry.

Default

With no value or state properties supplied, Input exposes an empty, editable native control.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>

<ui-input
  type="text"
  aria-label="Office search"
  placeholder="Search Fellowship office"
> </ui-input>
<script setup>
  import { UiInput } from "@threadlabs/looma";
</script>

<template>
  <UiInput
    type="text"
    aria-label="Office search"
    placeholder="Search Fellowship office"
  > </UiInput>
</template>
import { UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiInput
    type="text"
    aria-label="Office search"
    placeholder="Search Fellowship office"
  > </UiInput>;
}
/** @jsxImportSource preact */
import { UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiInput
    type="text"
    aria-label="Office search"
    placeholder="Search Fellowship office"
  > </UiInput>;
}
<script>
  import { UiInput } from "@threadlabs/looma";
</script>

<UiInput
  type="text"
  aria-label="Office search"
  placeholder="Search Fellowship office"
> </UiInput>
import { Component } from "@angular/core";
import { UiInput } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiInput],
  template: `
    <input
      ui-input
      [type]="'text'"
      aria-label="Office search"
      placeholder="Search Fellowship office"
    >
  `,
})
export default class Example {}

invalid, readonly, and disabled

These properties configure validation and availability on the native input Looma renders.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>

<ui-stack
  gap="s"
>
  <ui-input
    invalid
    value="Invalid"
    aria-label="Invalid"
  > </ui-input>
  <ui-input
    readonly
    value="Read only"
    aria-label="Read only"
  > </ui-input>
  <ui-input
    disabled
    value="Disabled"
    aria-label="Disabled"
  > </ui-input>
</ui-stack>
<script setup>
  import { UiInput, UiStack } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="s"
  >
    <UiInput
      :invalid="true"
      value="Invalid"
      aria-label="Invalid"
    > </UiInput>
    <UiInput
      :readonly="true"
      value="Read only"
      aria-label="Read only"
    > </UiInput>
    <UiInput
      :disabled="true"
      value="Disabled"
      aria-label="Disabled"
    > </UiInput>
  </UiStack>
</template>
import { UiInput, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiInput
      invalid
      value="Invalid"
      aria-label="Invalid"
    > </UiInput>
    <UiInput
      readOnly
      value="Read only"
      aria-label="Read only"
    > </UiInput>
    <UiInput
      disabled
      value="Disabled"
      aria-label="Disabled"
    > </UiInput>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiInput, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiInput
      invalid
      value="Invalid"
      aria-label="Invalid"
    > </UiInput>
    <UiInput
      readOnly
      value="Read only"
      aria-label="Read only"
    > </UiInput>
    <UiInput
      disabled
      value="Disabled"
      aria-label="Disabled"
    > </UiInput>
  </UiStack>;
}
<script>
  import { UiInput, UiStack } from "@threadlabs/looma";
</script>

<UiStack
  gap="s"
>
  <UiInput
    invalid
    value="Invalid"
    aria-label="Invalid"
  > </UiInput>
  <UiInput
    readonly
    value="Read only"
    aria-label="Read only"
  > </UiInput>
  <UiInput
    disabled
    value="Disabled"
    aria-label="Disabled"
  > </UiInput>
</UiStack>
import { Component } from "@angular/core";
import { UiInput, UiStack } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiInput],
  template: `
    <div
      ui-stack
      [gap]="'s'"
    >
      <input
        ui-input
        [invalid]="true"
        [value]="'Invalid'"
        aria-label="Invalid"
      >
      <input
        ui-input
        [readonly]="true"
        [value]="'Read only'"
        aria-label="Read only"
      >
      <input
        ui-input
        [disabled]="true"
        [value]="'Disabled'"
        aria-label="Disabled"
      >
    </div>
  `,
})
export default class Example {}

required

Required marks the field for native form validation and exposes the requirement to assistive technology.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>

<ui-input
  aria-label="Email"
  placeholder="you@example.com"
  required
> </ui-input>
<script setup>
  import { UiInput } from "@threadlabs/looma";
</script>

<template>
  <UiInput
    aria-label="Email"
    placeholder="you@example.com"
    :required="true"
  > </UiInput>
</template>
import { UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiInput
    aria-label="Email"
    placeholder="you@example.com"
    required
  > </UiInput>;
}
/** @jsxImportSource preact */
import { UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiInput
    aria-label="Email"
    placeholder="you@example.com"
    required
  > </UiInput>;
}
<script>
  import { UiInput } from "@threadlabs/looma";
</script>

<UiInput
  aria-label="Email"
  placeholder="you@example.com"
  required
> </UiInput>
import { Component } from "@angular/core";
import { UiInput } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiInput],
  template: `
    <input
      ui-input
      aria-label="Email"
      placeholder="you@example.com"
      [required]="true"
    >
  `,
})
export default class Example {}

size

Each size matches Button's (sm 32px, md 40px, lg 48px), so a field and a button of the same size share a row.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-stack
  gap="s"
>
  <ui-cluster
    gap="s"
    align="center"
  >
    <ui-input
      size="sm"
      aria-label="Small"
      placeholder="Small"
      style="inline-size: 14rem"
    ></ui-input>
    <ui-button
      size="sm"
    >Small</ui-button>
  </ui-cluster>
  <ui-cluster
    gap="s"
    align="center"
  >
    <ui-input
      aria-label="Medium"
      placeholder="Medium"
      style="inline-size: 14rem"
    ></ui-input>
    <ui-button>Medium</ui-button>
  </ui-cluster>
  <ui-cluster
    gap="s"
    align="center"
  >
    <ui-input
      size="lg"
      aria-label="Large"
      placeholder="Large"
      style="inline-size: 14rem"
    ></ui-input>
    <ui-button
      size="lg"
    >Large</ui-button>
  </ui-cluster>
</ui-stack>
<script setup>
  import { UiButton, UiCluster, UiInput, UiStack } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="s"
  >
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        size="sm"
        aria-label="Small"
        placeholder="Small"
        style="inline-size: 14rem"
      />
      <UiButton
        size="sm"
      >Small</UiButton>
    </UiCluster>
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        aria-label="Medium"
        placeholder="Medium"
        style="inline-size: 14rem"
      />
      <UiButton>Medium</UiButton>
    </UiCluster>
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        size="lg"
        aria-label="Large"
        placeholder="Large"
        style="inline-size: 14rem"
      />
      <UiButton
        size="lg"
      >Large</UiButton>
    </UiCluster>
  </UiStack>
</template>
import { UiButton, UiCluster, UiInput, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        size="sm"
        aria-label="Small"
        placeholder="Small"
        style={{"inlineSize":"14rem"}}
      />
      <UiButton
        size="sm"
      >Small</UiButton>
    </UiCluster>
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        aria-label="Medium"
        placeholder="Medium"
        style={{"inlineSize":"14rem"}}
      />
      <UiButton>Medium</UiButton>
    </UiCluster>
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        size="lg"
        aria-label="Large"
        placeholder="Large"
        style={{"inlineSize":"14rem"}}
      />
      <UiButton
        size="lg"
      >Large</UiButton>
    </UiCluster>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiButton, UiCluster, UiInput, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        size="sm"
        aria-label="Small"
        placeholder="Small"
        style={{"inlineSize":"14rem"}}
      />
      <UiButton
        size="sm"
      >Small</UiButton>
    </UiCluster>
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        aria-label="Medium"
        placeholder="Medium"
        style={{"inlineSize":"14rem"}}
      />
      <UiButton>Medium</UiButton>
    </UiCluster>
    <UiCluster
      gap="s"
      align="center"
    >
      <UiInput
        size="lg"
        aria-label="Large"
        placeholder="Large"
        style={{"inlineSize":"14rem"}}
      />
      <UiButton
        size="lg"
      >Large</UiButton>
    </UiCluster>
  </UiStack>;
}
<script>
  import { UiButton, UiCluster, UiInput, UiStack } from "@threadlabs/looma";
</script>

<UiStack
  gap="s"
>
  <UiCluster
    gap="s"
    align="center"
  >
    <UiInput
      size="sm"
      aria-label="Small"
      placeholder="Small"
      style="inline-size: 14rem"
    />
    <UiButton
      size="sm"
    >Small</UiButton>
  </UiCluster>
  <UiCluster
    gap="s"
    align="center"
  >
    <UiInput
      aria-label="Medium"
      placeholder="Medium"
      style="inline-size: 14rem"
    />
    <UiButton>Medium</UiButton>
  </UiCluster>
  <UiCluster
    gap="s"
    align="center"
  >
    <UiInput
      size="lg"
      aria-label="Large"
      placeholder="Large"
      style="inline-size: 14rem"
    />
    <UiButton
      size="lg"
    >Large</UiButton>
  </UiCluster>
</UiStack>
import { Component } from "@angular/core";
import { UiButton, UiCluster, UiInput, UiStack } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiCluster, UiInput, UiButton],
  template: `
    <div
      ui-stack
      [gap]="'s'"
    >
      <div
        ui-cluster
        [gap]="'s'"
        [align]="'center'"
      >
        <input
          ui-input
          [size]="'sm'"
          aria-label="Small"
          placeholder="Small"
          style="inline-size: 14rem"
        >
        <ng-template
          ui-button
          [size]="'sm'"
        >Small</ng-template>
      </div>
      <div
        ui-cluster
        [gap]="'s'"
        [align]="'center'"
      >
        <input
          ui-input
          aria-label="Medium"
          placeholder="Medium"
          style="inline-size: 14rem"
        >
        <ng-template
          ui-button
        >Medium</ng-template>
      </div>
      <div
        ui-cluster
        [gap]="'s'"
        [align]="'center'"
      >
        <input
          ui-input
          [size]="'lg'"
          aria-label="Large"
          placeholder="Large"
          style="inline-size: 14rem"
        >
        <ng-template
          ui-button
          [size]="'lg'"
        >Large</ng-template>
      </div>
    </div>
  `,
})
export default class Example {}

Compact row

size="sm" across a row. Inputs, selects, comboboxes, and buttons shrink to the small control height; a checkbox, radio, or switch keeps its box and centres its label in that height, so every control and label lines up.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-select/ui-select.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-combobox/ui-combobox.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-cluster
  gap="s"
  align="center"
>
  <ui-input
    size="sm"
    type="search"
    aria-label="Search orders"
    placeholder="Search orders"
    style="inline-size: 12rem"
  ></ui-input>
  <ui-select
    size="sm"
    aria-label="Status"
    style="inline-size: 9rem"
  >
    <option
      value
    >Any status</option>
    <option
      value="open"
    >Open</option>
    <option
      value="closed"
    >Closed</option>
  </ui-select>
  <ui-combobox
    size="sm"
    label="Owner"
    label-visibility="sr-only"
    placeholder="Owner"
    disclosure
    style="inline-size: 10rem"
  >
    <option
      value="ada"
    >Ada</option>
    <option
      value="grace"
    >Grace</option>
  </ui-combobox>
  <ui-checkbox
    size="sm"
  >Mine only</ui-checkbox>
  <ui-radio
    size="sm"
    name="compact-row-range"
    value="week"
    checked
  >This week</ui-radio>
  <ui-radio
    size="sm"
    name="compact-row-range"
    value="month"
  >This month</ui-radio>
  <ui-switch
    size="sm"
  >Archived</ui-switch>
  <ui-button
    size="sm"
    variant="solid"
  >Apply</ui-button>
</ui-cluster>
<script setup>
  import {
    UiButton,
    UiCheckbox,
    UiCluster,
    UiCombobox,
    UiInput,
    UiRadio,
    UiSelect,
    UiSwitch,
  } from "@threadlabs/looma";
</script>

<template>
  <UiCluster
    gap="s"
    align="center"
  >
    <UiInput
      size="sm"
      type="search"
      aria-label="Search orders"
      placeholder="Search orders"
      style="inline-size: 12rem"
    />
    <UiSelect
      size="sm"
      aria-label="Status"
      style="inline-size: 9rem"
    >
      <option
        value=""
      >Any status</option>
      <option
        value="open"
      >Open</option>
      <option
        value="closed"
      >Closed</option>
    </UiSelect>
    <UiCombobox
      size="sm"
      label="Owner"
      label-visibility="sr-only"
      placeholder="Owner"
      :disclosure="true"
      style="inline-size: 10rem"
    >
      <option
        value="ada"
      >Ada</option>
      <option
        value="grace"
      >Grace</option>
    </UiCombobox>
    <UiCheckbox
      size="sm"
    >Mine only</UiCheckbox>
    <UiRadio
      size="sm"
      name="compact-row-range"
      value="week"
      :checked="true"
    >This week</UiRadio>
    <UiRadio
      size="sm"
      name="compact-row-range"
      value="month"
    >This month</UiRadio>
    <UiSwitch
      size="sm"
    >Archived</UiSwitch>
    <UiButton
      size="sm"
      variant="solid"
    >Apply</UiButton>
  </UiCluster>
</template>
import {
  UiButton,
  UiCheckbox,
  UiCluster,
  UiCombobox,
  UiInput,
  UiRadio,
  UiSelect,
  UiSwitch,
} from "@threadlabs/looma";

export default function Example() {
  return <UiCluster
    gap="s"
    align="center"
  >
    <UiInput
      size="sm"
      type="search"
      aria-label="Search orders"
      placeholder="Search orders"
      style={{"inlineSize":"12rem"}}
    />
    <UiSelect
      size="sm"
      aria-label="Status"
      style={{"inlineSize":"9rem"}}
    >
      <option
        value=""
      >Any status</option>
      <option
        value="open"
      >Open</option>
      <option
        value="closed"
      >Closed</option>
    </UiSelect>
    <UiCombobox
      size="sm"
      label="Owner"
      labelVisibility="sr-only"
      placeholder="Owner"
      disclosure
      style={{"inlineSize":"10rem"}}
    >
      <option
        value="ada"
      >Ada</option>
      <option
        value="grace"
      >Grace</option>
    </UiCombobox>
    <UiCheckbox
      size="sm"
    >Mine only</UiCheckbox>
    <UiRadio
      size="sm"
      name="compact-row-range"
      value="week"
      checked
    >This week</UiRadio>
    <UiRadio
      size="sm"
      name="compact-row-range"
      value="month"
    >This month</UiRadio>
    <UiSwitch
      size="sm"
    >Archived</UiSwitch>
    <UiButton
      size="sm"
      variant="solid"
    >Apply</UiButton>
  </UiCluster>;
}
/** @jsxImportSource preact */
import {
  UiButton,
  UiCheckbox,
  UiCluster,
  UiCombobox,
  UiInput,
  UiRadio,
  UiSelect,
  UiSwitch,
} from "@threadlabs/looma";

export default function Example() {
  return <UiCluster
    gap="s"
    align="center"
  >
    <UiInput
      size="sm"
      type="search"
      aria-label="Search orders"
      placeholder="Search orders"
      style={{"inlineSize":"12rem"}}
    />
    <UiSelect
      size="sm"
      aria-label="Status"
      style={{"inlineSize":"9rem"}}
    >
      <option
        value=""
      >Any status</option>
      <option
        value="open"
      >Open</option>
      <option
        value="closed"
      >Closed</option>
    </UiSelect>
    <UiCombobox
      size="sm"
      label="Owner"
      labelVisibility="sr-only"
      placeholder="Owner"
      disclosure
      style={{"inlineSize":"10rem"}}
    >
      <option
        value="ada"
      >Ada</option>
      <option
        value="grace"
      >Grace</option>
    </UiCombobox>
    <UiCheckbox
      size="sm"
    >Mine only</UiCheckbox>
    <UiRadio
      size="sm"
      name="compact-row-range"
      value="week"
      checked
    >This week</UiRadio>
    <UiRadio
      size="sm"
      name="compact-row-range"
      value="month"
    >This month</UiRadio>
    <UiSwitch
      size="sm"
    >Archived</UiSwitch>
    <UiButton
      size="sm"
      variant="solid"
    >Apply</UiButton>
  </UiCluster>;
}
<script>
  import {
    UiButton,
    UiCheckbox,
    UiCluster,
    UiCombobox,
    UiInput,
    UiRadio,
    UiSelect,
    UiSwitch,
  } from "@threadlabs/looma";
</script>

<UiCluster
  gap="s"
  align="center"
>
  <UiInput
    size="sm"
    type="search"
    aria-label="Search orders"
    placeholder="Search orders"
    style="inline-size: 12rem"
  />
  <UiSelect
    size="sm"
    aria-label="Status"
    style="inline-size: 9rem"
  >
    <option
      value=""
    >Any status</option>
    <option
      value="open"
    >Open</option>
    <option
      value="closed"
    >Closed</option>
  </UiSelect>
  <UiCombobox
    size="sm"
    label="Owner"
    labelVisibility="sr-only"
    placeholder="Owner"
    disclosure
    style="inline-size: 10rem"
  >
    <option
      value="ada"
    >Ada</option>
    <option
      value="grace"
    >Grace</option>
  </UiCombobox>
  <UiCheckbox
    size="sm"
  >Mine only</UiCheckbox>
  <UiRadio
    size="sm"
    name="compact-row-range"
    value="week"
    checked
  >This week</UiRadio>
  <UiRadio
    size="sm"
    name="compact-row-range"
    value="month"
  >This month</UiRadio>
  <UiSwitch
    size="sm"
  >Archived</UiSwitch>
  <UiButton
    size="sm"
    variant="solid"
  >Apply</UiButton>
</UiCluster>
import { Component } from "@angular/core";
import {
  UiButton,
  UiCheckbox,
  UiCluster,
  UiCombobox,
  UiInput,
  UiRadio,
  UiSelect,
  UiSwitch,
} from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCluster, UiInput, UiSelect, UiCombobox, UiCheckbox, UiRadio, UiSwitch, UiButton],
  template: `
    <div
      ui-cluster
      [gap]="'s'"
      [align]="'center'"
    >
      <input
        ui-input
        [size]="'sm'"
        [type]="'search'"
        aria-label="Search orders"
        placeholder="Search orders"
        style="inline-size: 12rem"
      >
      <select
        ui-select
        [size]="'sm'"
        aria-label="Status"
        style="inline-size: 9rem"
      >
        <option
          value=""
        >Any status</option>
        <option
          value="open"
        >Open</option>
        <option
          value="closed"
        >Closed</option>
      </select>
      <div
        ui-combobox
        [size]="'sm'"
        [label]="'Owner'"
        [labelVisibility]="'sr-only'"
        [placeholder]="'Owner'"
        [disclosure]="true"
        style="inline-size: 10rem"
      >
        <option
          value="ada"
        >Ada</option>
        <option
          value="grace"
        >Grace</option>
      </div>
      <label
        ui-checkbox
        [size]="'sm'"
      >Mine only</label>
      <label
        ui-radio
        [size]="'sm'"
        [name]="'compact-row-range'"
        [value]="'week'"
        [checked]="true"
      >This week</label>
      <label
        ui-radio
        [size]="'sm'"
        [name]="'compact-row-range'"
        [value]="'month'"
      >This month</label>
      <label
        ui-switch
        [size]="'sm'"
      >Archived</label>
      <ng-template
        ui-button
        [size]="'sm'"
        [variant]="'solid'"
      >Apply</ng-template>
    </div>
  `,
})
export default class Example {}

Edit blocks within one field

Focus selects the first block. Type digits to advance automatically, or type the colon to move to minutes. Arrow keys select neighbouring blocks; Backspace clears or moves back. Date Field and Time Field supply their localized blocks automatically.

Type hours, then minutes. You can also type the colon to advance.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-form-field/ui-form-field.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>

<ui-form-field>
  <label
    slot="label"
    for="segmented-time"
  >Time (24-hour)</label>
  <ui-input
    id="segmented-time"
    name="time"
    value="09:30"
    inputmode="numeric"
    segments="[{ placeholder: &quot;hh&quot;, pattern: &quot;[0-9]&quot; }, { literal: &quot;:&quot; }, { placeholder: &quot;mm&quot;, pattern: &quot;[0-9]&quot; }]"
  ></ui-input>
  <small
    slot="help"
  >Type hours, then minutes. You can also type the colon to advance.</small>
</ui-form-field>
<script setup>
  import { UiFormField, UiInput } from "@threadlabs/looma";
</script>

<template>
  <UiFormField>
    <template #label>
      <label
        for="segmented-time"
      >Time (24-hour)</label>
    </template>
    <UiInput
      id="segmented-time"
      name="time"
      value="09:30"
      inputmode="numeric"
      :segments="[{ placeholder: 'hh', pattern: '[0-9]' }, { literal: ':' }, { placeholder: 'mm', pattern: '[0-9]' }]"
    />
    <template #help>
      <small>Type hours, then minutes. You can also type the colon to advance.</small>
    </template>
  </UiFormField>
</template>
import { UiFormField, UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiFormField
    slots={{
      label: <label
        htmlFor="segmented-time"
      >Time (24-hour)</label>,
      help: <small>Type hours, then minutes. You can also type the colon to advance.</small>,
    }}
  >
    <UiInput
      id="segmented-time"
      name="time"
      value="09:30"
      inputmode="numeric"
      segments={[{"placeholder":"hh","pattern":"[0-9]"},{"literal":":"},{"placeholder":"mm","pattern":"[0-9]"}]}
    />
  </UiFormField>;
}
/** @jsxImportSource preact */
import { UiFormField, UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiFormField
    slots={{
      label: <label
        htmlFor="segmented-time"
      >Time (24-hour)</label>,
      help: <small>Type hours, then minutes. You can also type the colon to advance.</small>,
    }}
  >
    <UiInput
      id="segmented-time"
      name="time"
      value="09:30"
      inputmode="numeric"
      segments={[{"placeholder":"hh","pattern":"[0-9]"},{"literal":":"},{"placeholder":"mm","pattern":"[0-9]"}]}
    />
  </UiFormField>;
}
<script>
  import { UiFormField, UiInput } from "@threadlabs/looma";
</script>

{#snippet label()}
  <label
    for="segmented-time"
  >Time (24-hour)</label>
{/snippet}

{#snippet help()}
  <small>Type hours, then minutes. You can also type the colon to advance.</small>
{/snippet}

<UiFormField
  slots={{ label, help }}
>
  <UiInput
    id="segmented-time"
    name="time"
    value="09:30"
    inputmode="numeric"
    segments={[{"placeholder":"hh","pattern":"[0-9]"},{"literal":":"},{"placeholder":"mm","pattern":"[0-9]"}]}
  />
</UiFormField>
import { Component } from "@angular/core";
import { UiFormField, UiInput } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiFormField, UiInput],
  template: `
    <div
      ui-form-field
    >
      <ng-container ngProjectAs="[slot='label']">
        <label
          for="segmented-time"
        >Time (24-hour)</label>
      </ng-container>
      <input
        ui-input
        id="segmented-time"
        name="time"
        [value]="'09:30'"
        inputmode="numeric"
        [segments]="[{ placeholder: 'hh', pattern: '[0-9]' }, { literal: ':' }, { placeholder: 'mm', pattern: '[0-9]' }]"
      >
      <ng-container ngProjectAs="[slot='help']">
        <small>Type hours, then minutes. You can also type the colon to advance.</small>
      </ng-container>
    </div>
  `,
})
export default class Example {}

Enter an authentication code

Each digit advances to the next block. Backspace moves backwards, and a pasted code fills the remaining blocks. One native input retains browser autofill, clipboard and form behavior.

Enter the six digits from your authentication message.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-form-field/ui-form-field.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>

<ui-form-field
  required
>
  <label
    slot="label"
    for="authentication-code"
  >Authentication code</label>
  <ui-input
    id="authentication-code"
    name="code"
    inputmode="numeric"
    autocomplete="one-time-code"
    placeholder="······"
    pattern="[0-9]{6}"
    segments="[{ placeholder: &quot;·&quot;, pattern: &quot;[0-9]&quot; }, { placeholder: &quot;·&quot;, pattern: &quot;[0-9]&quot; }, { placeholder: &quot;·&quot;, pattern: &quot;[0-9]&quot; }, { placeholder: &quot;·&quot;, pattern: &quot;[0-9]&quot; }, { placeholder: &quot;·&quot;, pattern: &quot;[0-9]&quot; }, { placeholder: &quot;·&quot;, pattern: &quot;[0-9]&quot; }]"
  ></ui-input>
  <small
    slot="help"
  >Enter the six digits from your authentication message.</small>
</ui-form-field>
<script setup>
  import { UiFormField, UiInput } from "@threadlabs/looma";
</script>

<template>
  <UiFormField
    :required="true"
  >
    <template #label>
      <label
        for="authentication-code"
      >Authentication code</label>
    </template>
    <UiInput
      id="authentication-code"
      name="code"
      inputmode="numeric"
      autocomplete="one-time-code"
      placeholder="······"
      pattern="[0-9]&#123;6}"
      :segments="[{ placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }]"
    />
    <template #help>
      <small>Enter the six digits from your authentication message.</small>
    </template>
  </UiFormField>
</template>
import { UiFormField, UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiFormField
    required
    slots={{
      label: <label
        htmlFor="authentication-code"
      >Authentication code</label>,
      help: <small>Enter the six digits from your authentication message.</small>,
    }}
  >
    <UiInput
      id="authentication-code"
      name="code"
      inputmode="numeric"
      autocomplete="one-time-code"
      placeholder="······"
      pattern="[0-9]{6}"
      segments={[{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"}]}
    />
  </UiFormField>;
}
/** @jsxImportSource preact */
import { UiFormField, UiInput } from "@threadlabs/looma";

export default function Example() {
  return <UiFormField
    required
    slots={{
      label: <label
        htmlFor="authentication-code"
      >Authentication code</label>,
      help: <small>Enter the six digits from your authentication message.</small>,
    }}
  >
    <UiInput
      id="authentication-code"
      name="code"
      inputmode="numeric"
      autocomplete="one-time-code"
      placeholder="······"
      pattern="[0-9]{6}"
      segments={[{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"}]}
    />
  </UiFormField>;
}
<script>
  import { UiFormField, UiInput } from "@threadlabs/looma";
</script>

{#snippet label()}
  <label
    for="authentication-code"
  >Authentication code</label>
{/snippet}

{#snippet help()}
  <small>Enter the six digits from your authentication message.</small>
{/snippet}

<UiFormField
  required
  slots={{ label, help }}
>
  <UiInput
    id="authentication-code"
    name="code"
    inputmode="numeric"
    autocomplete="one-time-code"
    placeholder="······"
    pattern="[0-9]&#123;6}"
    segments={[{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"},{"placeholder":"·","pattern":"[0-9]"}]}
  />
</UiFormField>
import { Component } from "@angular/core";
import { UiFormField, UiInput } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiFormField, UiInput],
  template: `
    <div
      ui-form-field
      [required]="true"
    >
      <ng-container ngProjectAs="[slot='label']">
        <label
          for="authentication-code"
        >Authentication code</label>
      </ng-container>
      <input
        ui-input
        id="authentication-code"
        name="code"
        inputmode="numeric"
        autocomplete="one-time-code"
        placeholder="······"
        pattern="[0-9]{6}"
        [segments]="[{ placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }, { placeholder: '·', pattern: '[0-9]' }]"
      >
      <ng-container ngProjectAs="[slot='help']">
        <small>Enter the six digits from your authentication message.</small>
      </ng-container>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: input

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
disabledPrevents editing and form submission.disabledbooleanfalse—
invalidExposes invalid state to assistive technology and styling.invalidbooleanfalse—
readonlyAllows selection without allowing edits.readonlybooleanfalse—
requiredMarks the control as required for form validation.requiredbooleanfalse—
segmentsOptional text-entry blocks. A placeholder defines an editable block’s maximum length; pattern matches one allowed Unicode character. A literal is skipped automatically or by typing it. Focus selects the first block; arrows and deletion move between blocks. Native paste, composition and autofill remain available. Placeholder text is never inserted into the value; empty fields use the native placeholder attribute. Applies to text, tel and search inputs.segmentslist(object)
  • [].placeholder: string
  • [].pattern: string
  • [].literal: string
——
sizeHeight, padding, and type size, matching Button's sizes so a field and a button of one size share a row. sm is the small control height (2rem by default) with smaller text, for compact rows such as a table row or a filter bar; md is the standard control height; lg is taller with roomier padding. Touch input keeps body-size field text without inflating the visible size ladder. Change it after render through this option; the element's size property is the native one.sizekeywordmdsm, md, lg
typeThe native input mode. Number and range modes accept numeric values; the other supported modes accept strings. File inputs expose selected files through the native change event's target.files. Their value can only be set to an empty string to clear the selection.typekeywordtexttext, search, tel, url, email, password, date, month, week, time, datetime-local, number, range, color, file
valueThe input's value: text, or a number for type="number" and type="range" fields. Setting or changing it replaces the live text; between changes the user's typing stands, so follow input events to keep it in step. A number field keeps text that means the same number, such as 1.0 for 1. It is also the input's default, so a form reset returns to it. Native edits made before the first browser connection stay live when that external value is unchanged.valuetype=number, range → number; otherwise → string——

Framework props

NameTypeDefaultAllowed values
disabledbooleanfalse—
invalidbooleanfalse—
readonlybooleanfalse—
requiredbooleanfalse—
segmentsreadonly ({ readonly placeholder?: string | null; readonly pattern?: string | null; readonly literal?: string | null })[]
  • [].placeholder: string
  • [].pattern: string
  • [].literal: string
——
size"sm" | "md" | "lg"mdsm, md, lg
type"text" | "search" | "tel" | "url" | "email" | "password" | "date" | "month" | "week" | "time" | "datetime-local" | "number" | "range" | "color" | "file"texttext, search, tel, url, email, password, date, month, week, time, datetime-local, number, range, color, file
valuestring | number——

Events

No events.

Slots

No slots.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-input-border—var(--ui-control-border, var(--ui-border-strong))
--ui-input-border-hover—var(--ui-control-border-hover, var(--ui-text-secondary))
--ui-input-border-width—var(--ui-border-width)
--ui-input-focus-border—var(--ui-control-focus, var(--ui-accent-solid))
--ui-input-focus-outline—var(--ui-focus-width) solid Highlight
--ui-input-focus-shadow—var(--ui-control-inset), var(--ui-control-focus-halo-shadow)
--ui-input-inline-size—100%
--ui-input-invalid-border—var(--ui-field-danger, var(--ui-danger-solid))
--ui-input-invalid-focus-shadow—var( --_ui-default-input-invalid-focus-shadow, var(--ui-control-inset), 0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix( in srgb, var(--ui-input-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%, transparent ) )
--ui-input-min-block-size—var(--ui-control-size-md) | var(--ui-control-size-sm) | var(--ui-control-size-lg)
--ui-input-radius—var(--ui-field-radius, var(--ui-radius-md))
--ui-input-shadow—var(--ui-control-inset)
--ui-input-surface—var(--ui-control-surface, var(--ui-surface-elevated))

Shared tokens

TokenFallback
--ui-accent-solid—
--ui-border-strong—
--ui-border-width—
--ui-control-bordervar(--ui-border-strong)
--ui-control-border-hovervar(--ui-text-secondary)
--ui-control-focusvar(--ui-accent-solid)
--ui-control-focus-halo-shadow—
--ui-control-inset—
--ui-control-inset-block-lgvar(--ui-space-3)
--ui-control-inset-block-mdvar(--ui-space-2)
--ui-control-inset-block-smvar(--ui-space-1)
--ui-control-placeholdervar(--ui-text-secondary)
--ui-control-size-lg—
--ui-control-size-md—
--ui-control-size-sm—
--ui-control-surfacevar(--ui-surface-elevated)
--ui-control-surface-disabledvar(--ui-surface-muted)
--ui-danger-solid—
--ui-disabled-text—
--ui-field-dangervar(--ui-danger-solid)
--ui-field-radiusvar(--ui-radius-md)
--ui-focus-width—
--ui-font-size-md—
--ui-font-size-sm—
--ui-line-height-tight—
--ui-motion-ease—
--ui-motion-fast—
--ui-radius-md—
--ui-space-1—
--ui-space-2—
--ui-space-3—
--ui-space-4—
--ui-surface-elevated—
--ui-surface-highlightnone
--ui-surface-muted—
--ui-text-primary—
--ui-text-secondary—