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
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
autosize | Grow with the text while keeping rows as the minimum visible height. Uses CSS field sizing where supported; other browsers keep the native rows behavior. | autosize | boolean | false | — |
disabled | Prevents editing and form submission. | disabled | boolean | false | — |
invalid | Exposes invalid state to assistive technology and styling. | invalid | boolean | false | — |
max-rows | Optional maximum visible rows, for a growing field in a bounded layout. Extra text scrolls inside the field. Omit it to retain unbounded growth. | maxRows | integer | — | — |
readonly | Allows selection without allowing edits. | readonly | boolean | false | — |
required | Marks the control as required for form validation. | required | boolean | false | — |
rows | Initial number of visible text rows. | rows | integer | 4 | — |
size | Padding 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. | size | keyword | md | sm, md, lg |
value | The 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. | value | string | — | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
autosize | boolean | false | — |
disabled | boolean | false | — |
invalid | boolean | false | — |
maxRows | number | — | — |
readonly | boolean | false | — |
required | boolean | false | — |
rows | number | 4 | — |
size | "sm" | "md" | "lg" | md | sm, md, lg |
value | string | — | — |
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
| Token | Declared values | Fallback |
|---|---|---|
--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
| Token | Fallback |
|---|---|
--ui-accent-solid | — |
--ui-border-strong | — |
--ui-border-width | — |
--ui-control-border | var(--ui-border-strong) |
--ui-control-border-hover | var(--ui-text-secondary) |
--ui-control-focus | var(--ui-accent-solid) |
--ui-control-focus-halo-shadow | — |
--ui-control-inset | — |
--ui-control-placeholder | var(--ui-text-secondary) |
--ui-control-surface | var(--ui-surface-elevated) |
--ui-control-surface-disabled | var(--ui-surface-muted) |
--ui-danger-solid | — |
--ui-disabled-text | — |
--ui-field-danger | var(--ui-danger-solid) |
--ui-field-radius | var(--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-highlight | none |
--ui-surface-muted | — |
--ui-text-primary | — |
--ui-text-secondary | — |