Textarea

Multiline input that lowers directly to a native <textarea> while applying Looma's shared control theme.

Default

With rows omitted, Textarea exposes four editable rows.

Code

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

<ui-textarea
  aria-label="Text"
  placeholder="Enter a longer message"
> </ui-textarea>
<script setup>
  import { UiTextarea } from "@threadlabs/looma";
</script>

<template>
  <UiTextarea
    aria-label="Text"
    placeholder="Enter a longer message"
  > </UiTextarea>
</template>
import { UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiTextarea
    aria-label="Text"
    placeholder="Enter a longer message"
  > </UiTextarea>;
}
/** @jsxImportSource preact */
import { UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiTextarea
    aria-label="Text"
    placeholder="Enter a longer message"
  > </UiTextarea>;
}
<script>
  import { UiTextarea } from "@threadlabs/looma";
</script>

<UiTextarea
  aria-label="Text"
  placeholder="Enter a longer message"
> </UiTextarea>
import { Component } from "@angular/core";
import { UiTextarea } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiTextarea],
  template: `
    <textarea
      ui-textarea
      aria-label="Text"
      placeholder="Enter a longer message"
    > </textarea>
  `,
})
export default class Example {}

rows, invalid, and readonly

Rows sets the visible height while invalid and readonly express validation and availability.

Code

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

<ui-stack
  gap="s"
>
  <ui-textarea
    rows="4"
    invalid
    value="Invalid"
    aria-label="Invalid"
  > </ui-textarea>
  <ui-textarea
    rows="3"
    readonly
    value="Read only"
    aria-label="Read only"
  > </ui-textarea>
</ui-stack>
<script setup>
  import { UiStack, UiTextarea } from "@threadlabs/looma";
</script>

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

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

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

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

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

disabled and required

Disabled makes the field unavailable; required marks it for native form validation.

Code

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

<ui-stack
  gap="s"
>
  <ui-textarea
    aria-label="Disabled notes"
    value="Unavailable"
    disabled
  > </ui-textarea>
  <ui-textarea
    aria-label="Required notes"
    placeholder="Required"
    required
  > </ui-textarea>
</ui-stack>
<script setup>
  import { UiStack, UiTextarea } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="s"
  >
    <UiTextarea
      aria-label="Disabled notes"
      value="Unavailable"
      :disabled="true"
    > </UiTextarea>
    <UiTextarea
      aria-label="Required notes"
      placeholder="Required"
      :required="true"
    > </UiTextarea>
  </UiStack>
</template>
import { UiStack, UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiTextarea
      aria-label="Disabled notes"
      value="Unavailable"
      disabled
    > </UiTextarea>
    <UiTextarea
      aria-label="Required notes"
      placeholder="Required"
      required
    > </UiTextarea>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiStack, UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiTextarea
      aria-label="Disabled notes"
      value="Unavailable"
      disabled
    > </UiTextarea>
    <UiTextarea
      aria-label="Required notes"
      placeholder="Required"
      required
    > </UiTextarea>
  </UiStack>;
}
<script>
  import { UiStack, UiTextarea } from "@threadlabs/looma";
</script>

<UiStack
  gap="s"
>
  <UiTextarea
    aria-label="Disabled notes"
    value="Unavailable"
    disabled
  > </UiTextarea>
  <UiTextarea
    aria-label="Required notes"
    placeholder="Required"
    required
  > </UiTextarea>
</UiStack>
import { Component } from "@angular/core";
import { UiStack, UiTextarea } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiTextarea],
  template: `
    <div
      ui-stack
      [gap]="'s'"
    >
      <textarea
        ui-textarea
        aria-label="Disabled notes"
        [value]="'Unavailable'"
        [disabled]="true"
      > </textarea>
      <textarea
        ui-textarea
        aria-label="Required notes"
        placeholder="Required"
        [required]="true"
      > </textarea>
    </div>
  `,
})
export default class Example {}

size

sm and lg set the padding and type size to match fields of the same size; rows still sets the height.

Code

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

<ui-stack
  gap="s"
>
  <ui-textarea
    size="sm"
    rows="2"
    aria-label="Small"
    placeholder="Small"
  ></ui-textarea>
  <ui-textarea
    size="lg"
    rows="2"
    aria-label="Large"
    placeholder="Large"
  ></ui-textarea>
</ui-stack>
<script setup>
  import { UiStack, UiTextarea } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="s"
  >
    <UiTextarea
      size="sm"
      :rows="2"
      aria-label="Small"
      placeholder="Small"
    />
    <UiTextarea
      size="lg"
      :rows="2"
      aria-label="Large"
      placeholder="Large"
    />
  </UiStack>
</template>
import { UiStack, UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiTextarea
      size="sm"
      rows={2}
      aria-label="Small"
      placeholder="Small"
    />
    <UiTextarea
      size="lg"
      rows={2}
      aria-label="Large"
      placeholder="Large"
    />
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiStack, UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
  >
    <UiTextarea
      size="sm"
      rows={2}
      aria-label="Small"
      placeholder="Small"
    />
    <UiTextarea
      size="lg"
      rows={2}
      aria-label="Large"
      placeholder="Large"
    />
  </UiStack>;
}
<script>
  import { UiStack, UiTextarea } from "@threadlabs/looma";
</script>

<UiStack
  gap="s"
>
  <UiTextarea
    size="sm"
    rows={2}
    aria-label="Small"
    placeholder="Small"
  />
  <UiTextarea
    size="lg"
    rows={2}
    aria-label="Large"
    placeholder="Large"
  />
</UiStack>
import { Component } from "@angular/core";
import { UiStack, UiTextarea } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiTextarea],
  template: `
    <div
      ui-stack
      [gap]="'s'"
    >
      <textarea
        ui-textarea
        [size]="'sm'"
        [rows]="2"
        aria-label="Small"
        placeholder="Small"
      ></textarea>
      <textarea
        ui-textarea
        [size]="'lg'"
        [rows]="2"
        aria-label="Large"
        placeholder="Large"
      ></textarea>
    </div>
  `,
})
export default class Example {}

Grow with content

Autosize grows and shrinks with the text. Rows remains the minimum visible height in browsers with CSS field sizing.

Code

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

<ui-textarea
  autosize
  rows="3"
  aria-label="Notes"
  placeholder="Add notes on separate lines"
></ui-textarea>
<script setup>
  import { UiTextarea } from "@threadlabs/looma";
</script>

<template>
  <UiTextarea
    :autosize="true"
    :rows="3"
    aria-label="Notes"
    placeholder="Add notes on separate lines"
  />
</template>
import { UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiTextarea
    autosize
    rows={3}
    aria-label="Notes"
    placeholder="Add notes on separate lines"
  />;
}
/** @jsxImportSource preact */
import { UiTextarea } from "@threadlabs/looma";

export default function Example() {
  return <UiTextarea
    autosize
    rows={3}
    aria-label="Notes"
    placeholder="Add notes on separate lines"
  />;
}
<script>
  import { UiTextarea } from "@threadlabs/looma";
</script>

<UiTextarea
  autosize
  rows={3}
  aria-label="Notes"
  placeholder="Add notes on separate lines"
/>
import { Component } from "@angular/core";
import { UiTextarea } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiTextarea],
  template: `
    <textarea
      ui-textarea
      [autosize]="true"
      [rows]="3"
      aria-label="Notes"
      placeholder="Add notes on separate lines"
    ></textarea>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: textarea

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
autosizeGrow with the text while keeping rows as the minimum visible height. Uses CSS field sizing where supported; other browsers keep the native rows behavior.autosizebooleanfalse—
disabledPrevents editing and form submission.disabledbooleanfalse—
invalidExposes invalid state to assistive technology and styling.invalidbooleanfalse—
max-rowsOptional maximum visible rows, for a growing field in a bounded layout. Extra text scrolls inside the field. Omit it to retain unbounded growth.maxRowsinteger——
readonlyAllows selection without allowing edits.readonlybooleanfalse—
requiredMarks the control as required for form validation.requiredbooleanfalse—
rowsInitial number of visible text rows.rowsinteger4—
sizePadding and type size, matching Input's sizes so a textarea sits with fields of the same size; rows sets its height. sm has tighter padding and smaller text, for compact forms; lg has roomier padding. Under a coarse pointer, sm keeps body-size text.sizekeywordmdsm, md, lg
valueThe textarea's text. Setting or changing it replaces the live text; between changes the user's typing stands, so follow input events to keep it in step. It is also the textarea's default, so a form reset returns to it.valuestring——

Framework props

NameTypeDefaultAllowed values
autosizebooleanfalse—
disabledbooleanfalse—
invalidbooleanfalse—
maxRowsnumber——
readonlybooleanfalse—
requiredbooleanfalse—
rowsnumber4—
size"sm" | "md" | "lg"mdsm, md, lg
valuestring——

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-textarea-border—var(--ui-control-border, var(--ui-border-strong))
--ui-textarea-border-hover—var(--ui-control-border-hover, var(--ui-text-secondary))
--ui-textarea-border-width—var(--ui-border-width)
--ui-textarea-focus-border—var(--ui-control-focus, var(--ui-accent-solid))
--ui-textarea-focus-outline—none
--ui-textarea-focus-shadow—var(--ui-control-inset), var(--ui-control-focus-halo-shadow)
--ui-textarea-invalid-border—var(--ui-field-danger, var(--ui-danger-solid))
--ui-textarea-invalid-focus-shadow—var( --_ui-default-textarea-invalid-focus-shadow, var(--ui-control-inset), 0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix( in srgb, var(--ui-textarea-invalid-border, var(--_ui-default-textarea-invalid-border, var(--ui-field-danger, var(--ui-danger-solid)))) 22%, transparent ) )
--ui-textarea-padding-inline-end—var(--ui-space-3) | var(--ui-space-2) | var(--ui-space-4)
--ui-textarea-radius—var(--ui-field-radius, var(--ui-radius-md))
--ui-textarea-shadow—var(--ui-control-inset)
--ui-textarea-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-placeholdervar(--ui-text-secondary)
--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-md—
--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—