Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

    Components

    Textarea

    Full supportSupported since v125.Full supportSupported since v152.Full supportSupported since v26.2.

    Anatomy

    • <Textarea>

      Container element.

    • slot="label"

      The label for the field.

    • slot="description"

      Description text displayed above the field.

    • .ui-field

      The boxed textarea area.

    • slot="header"

      Content above the textarea, inside the border, with a divider.

    • slot="prefix"

      Content at the inline-start of the field, inside the border.

    • <textarea>

      The textarea element.

    • slot="suffix"

      Content at the inline-end of the field, inside the border.

    • slot="footer"

      Content below the textarea, inside the border, with a divider.

    • slot="end-text"

      Supporting text displayed below the field.

    Variants

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea label="Default" placeholder="Placeholder" />
    <Textarea label="Filled" placeholder="Placeholder" filled />
    <label class="ui-textarea"
    ><span class="ui-label">Default</span
    ><span class="ui-field">
    <textarea placeholder="Placeholder"></textarea></span></label
    ><label class="ui-textarea ui-filled"
    ><span class="ui-label">Filled</span
    ><span class="ui-field"><textarea placeholder="Placeholder"></textarea></span
    ></label>

    Sizes

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea label="Small outlined" placeholder="Placeholder" small />
    <Textarea label="Small filled" placeholder="Placeholder" small filled />
    <label class="ui-textarea ui-small"
    ><span class="ui-label">Small outlined</span
    ><span class="ui-field">
    <textarea placeholder="Placeholder"></textarea></span></label
    ><label class="ui-textarea ui-filled ui-small"
    ><span class="ui-label">Small filled</span
    ><span class="ui-field"><textarea placeholder="Placeholder"></textarea></span
    ></label>

    End text

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea label="Label" placeholder="Default" endText="Supporting text" />
    <Textarea label="Label" placeholder="Filled" endText="Supporting text" filled />
    <label class="ui-textarea"
    ><span class="ui-label">Label</span
    ><span class="ui-field"><textarea placeholder="Default"></textarea></span
    ><span class="ui-end-text">Supporting text</span></label
    ><label class="ui-textarea ui-filled"
    ><span class="ui-label">Label</span
    ><span class="ui-field"><textarea placeholder="Filled"></textarea></span
    ><span class="ui-end-text">Supporting text</span></label
    >

    Affix

    Use the prefix, suffix, header, and footer slots to affix content inside the textarea's border. Header and footer are particularly useful for filenames and character counters.

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea label="Notes" placeholder="Add a note...">
    <svg
    slot="prefix"
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
    <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
    </svg>
    </Textarea>
    <label class="ui-textarea"
    ><span class="ui-label">Notes</span
    ><span class="ui-field">
    <textarea placeholder="Add a note..."></textarea
    ><span class="ui-prefix"
    ><svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <path
    d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
    ></path>
    <path
    d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
    ></path></svg></span></span
    ></label>

    Headers and footers

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea label="Code" placeholder="console.log('Hello, world!')">
    <Fragment slot="header">script.js</Fragment>
    </Textarea>
    <Textarea label="Comment" placeholder="Write a comment...">
    <Fragment slot="footer">0 / 280</Fragment>
    </Textarea>
    <label class="ui-textarea"
    ><span class="ui-label">Code</span
    ><span class="ui-field">
    <textarea placeholder="console.log('Hello, world!')"></textarea
    ><span class="ui-header">script.js</span></span
    ></label
    ><label class="ui-textarea"
    ><span class="ui-label">Comment</span
    ><span class="ui-field">
    <textarea placeholder="Write a comment..."></textarea
    ><span class="ui-footer">0 / 280</span></span
    ></label
    >

    Validation

    Set required on the component to toggle required styles on the textarea.

    Use the error prop to toggle invalid styles. It renders thedata-invalid attribute on the root element. Make use of the end text to give extra feedback on the error.

    ---
    import { Textarea } from "@opui/astro"
    ---
    <div class="example-row">
    <Textarea label="Label" placeholder="Default" required />
    <Textarea label="Label" placeholder="Filled" required filled />
    </div>
    <div class="example-row">
    <Textarea
    label="Label"
    placeholder="Default"
    endText="Only double-negatives are allowed."
    error
    />
    <Textarea
    label="Label"
    placeholder="Filled"
    endText="Only letters from the first half of the alphabet are allowed."
    error
    filled
    />
    </div>
    <div class="example-row">
    <label class="ui-textarea"
    ><span class="ui-label">Label</span
    ><span class="ui-field">
    <textarea placeholder="Default" required></textarea></span></label
    ><label class="ui-textarea ui-filled"
    ><span class="ui-label">Label</span
    ><span class="ui-field">
    <textarea placeholder="Filled" required></textarea></span
    ></label>
    </div>
    <div class="example-row">
    <label class="ui-textarea" data-invalid
    ><span class="ui-label">Label</span
    ><span class="ui-field"><textarea placeholder="Default"></textarea></span
    ><span class="ui-end-text">Only double-negatives are allowed.</span></label
    ><label class="ui-textarea ui-filled" data-invalid
    ><span class="ui-label">Label</span
    ><span class="ui-field"><textarea placeholder="Filled"></textarea></span
    ><span class="ui-end-text"
    >Only letters from the first half of the alphabet are allowed.</span
    ></label
    >
    </div>

    Spread

    Use the spread boolean prop to display the label and description on the left with the textarea on the right. The layout collapses to a column on narrow containers.

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea spread placeholder="Hello, world!">
    <Fragment slot="label">Message</Fragment>
    <Fragment slot="description"
    >You can write your message here. Keep it short, preferably under 100
    characters.</Fragment
    >
    </Textarea>
    <Textarea spread placeholder="Additional notes..." filled>
    <Fragment slot="label">Notes</Fragment>
    <Fragment slot="description">Add any additional notes or comments</Fragment>
    <Fragment slot="end-text">Maximum 500 characters</Fragment>
    </Textarea>
    <Textarea spread required label="Required">
    <Fragment slot="description">You must provide a response</Fragment>
    </Textarea>
    <Textarea spread disabled label="Disabled">
    <Fragment slot="description">This textarea is disabled</Fragment>
    </Textarea>
    <Textarea spread error label="Invalid Message">
    <Fragment slot="description">This textarea has an error</Fragment>
    <Fragment slot="end-text">This value is too short.</Fragment>
    </Textarea>
    <Textarea spread label="Bio" placeholder="Tell us about yourself...">
    <Fragment slot="description">Shown on your public profile</Fragment>
    <Fragment slot="prefix">
    <svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
    ></path>
    <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
    </svg>
    </Fragment>
    <Fragment slot="footer">280 characters left</Fragment>
    </Textarea>
    <Textarea
    spread
    filled
    label="Release notes"
    placeholder="Markdown supported..."
    >
    <Fragment slot="description">Shown on the changelog page</Fragment>
    <Fragment slot="header">v1.4.0</Fragment>
    <Fragment slot="footer">Saved 2 minutes ago</Fragment>
    <Fragment slot="end-text">Drafts are auto-saved</Fragment>
    </Textarea>
    <label class="ui-textarea ui-spread"
    ><span class="ui-label">Message</span
    ><span class="ui-start-text"
    >You can write your message here. Keep it short, preferably under 100
    characters.</span
    ><span class="ui-field">
    <textarea placeholder="Hello, world!"></textarea></span></label
    ><label class="ui-textarea ui-filled ui-spread"
    ><span class="ui-label">Notes</span
    ><span class="ui-start-text">Add any additional notes or comments</span
    ><span class="ui-field">
    <textarea placeholder="Additional notes..."></textarea></span
    ><span class="ui-end-text">Maximum 500 characters</span></label
    ><label class="ui-textarea ui-spread"
    ><span class="ui-label">Required</span
    ><span class="ui-start-text">You must provide a response</span
    ><span class="ui-field"><textarea required></textarea></span></label
    ><label class="ui-textarea ui-spread"
    ><span class="ui-label">Disabled</span
    ><span class="ui-start-text">This textarea is disabled</span
    ><span class="ui-field"><textarea disabled></textarea></span></label
    ><label class="ui-textarea ui-spread" data-invalid
    ><span class="ui-label">Invalid Message</span
    ><span class="ui-start-text">This textarea has an error</span
    ><span class="ui-field"><textarea></textarea></span
    ><span class="ui-end-text">This value is too short.</span></label
    ><label class="ui-textarea ui-spread"
    ><span class="ui-label">Bio</span
    ><span class="ui-start-text">Shown on your public profile</span
    ><span class="ui-field">
    <textarea placeholder="Tell us about yourself..."></textarea
    ><span class="ui-prefix"
    ><svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <path
    d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
    ></path>
    <path
    d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
    ></path></svg></span
    ><span class="ui-footer">280 characters left</span></span
    ></label
    ><label class="ui-textarea ui-filled ui-spread"
    ><span class="ui-label">Release notes</span
    ><span class="ui-start-text">Shown on the changelog page</span
    ><span class="ui-field">
    <textarea placeholder="Markdown supported..."></textarea
    ><span class="ui-header">v1.4.0</span
    ><span class="ui-footer">Saved 2 minutes ago</span></span
    ><span class="ui-end-text">Drafts are auto-saved</span></label
    >

    Auto-fit

    When enabled the Field changes size depending on its content.

    ---
    import { Textarea } from "@opui/astro"
    ---
    <Textarea label="Auto-fit" placeholder="Auto-fit" autoFit />
    <label class="ui-textarea ui-auto-fit"
    ><span class="ui-label">Auto-fit</span
    ><span class="ui-field"><textarea placeholder="Auto-fit"></textarea></span
    ></label>

    API

    Textarea API

    PropTypeDefaultDescription
    autoFitbooleanfalseChanges height depending on its content.
    descriptionstring-Description text displayed above the field.
    endTextstring-Supporting text displayed below the field.
    errorbooleanfalseShows error styles.
    filledbooleanfalseThe variant to use.
    idstring-The id of the <textarea>.
    labelstring-The label for the field.
    smallbooleanfalseThe size of the element.
    spreadbooleanfalsePushes the label and description to one side and the textarea to the other.

    Slots

    SlotDescription
    defaultExtra content inside the root.
    descriptionDescription text displayed above the field.
    end-textSupporting text displayed below the field.
    footerContent below the textarea, inside the border, with a divider.
    headerContent above the textarea, inside the border, with a divider.
    labelThe label for the field.
    prefixContent at the inline-start of the field, inside the border.
    suffixContent at the inline-end of the field, inside the border.
    supporting-textLegacy alias of the end-text slot.

    Textarea attributes (cols, disabled, maxlength, minlength, name, placeholder, required, rows, value) go to the <textarea>. Other attributes go to the root <label>.

    Browser support

    Full supportSupported since v125.Full supportSupported since v152.Full supportSupported since v26.2.

    See also the full browser support guide.

    Installation

    Dependencies

    @layer components.extended {
    /*
    - Common styling for input, textarea and select
    - Form related styles such as: label, supporting text, error handling
    */
    :where(.ui-text-field, .ui-textarea, .ui-select) {
    --_motion: var(--motion, 1);
    --_accent-color: var(--primary);
    --_bg-color: var(--surface-default);
    --_border-color: var(--field-border-color);
    --_field-padding-block: var(--size-2);
    --_field-padding-inline: var(--size-2);
    --_filled-border-color: var(--text-primary);
    --_height: var(--field-size);
    --_label-color: var(--text-muted);
    --_end-text-color: var(--text-muted);
    display: grid;
    gap: var(--size-1) 0;
    position: relative;
    /* Field - the actual input with a border around it */
    .ui-field {
    background-color: var(--_bg-color);
    border: var(--field-border-width) solid var(--_border-color);
    border-radius: var(--field-border-radius);
    display: grid;
    grid-column: 1/-1;
    grid-row: 2;
    grid-template-areas:
    "header header header"
    "prefix input suffix"
    "footer footer footer";
    grid-template-columns: auto 1fr auto;
    min-block-size: var(--_height);
    transition: border-color calc(0.2s * var(--_motion))
    cubic-bezier(0.4, 0, 0.2, 1);
    }
    :where(.ui-field) :where(input, textarea, select) {
    background: transparent;
    border: 0;
    color: var(--text-primary);
    font-family: var(--font-sans);
    font-size: var(--font-size-1);
    grid-area: input;
    inline-size: 100%;
    line-height: var(--font-lineheight-1);
    min-inline-size: 0;
    padding: var(--_field-padding-block) var(--_field-padding-inline);
    &:focus,
    &:focus-visible {
    outline: 0;
    }
    }
    /* Affixes */
    .ui-prefix,
    .ui-suffix {
    align-items: center;
    color: var(--_label-color);
    display: inline-flex;
    padding-inline: var(--_field-padding-inline);
    white-space: nowrap;
    }
    .ui-prefix {
    grid-area: prefix;
    }
    .ui-suffix {
    grid-area: suffix;
    }
    .ui-header,
    .ui-footer {
    align-items: center;
    color: var(--_label-color);
    display: flex;
    font-size: var(--font-size-0);
    gap: var(--size-2);
    padding: var(--size-1) var(--_field-padding-inline);
    }
    .ui-header {
    border-block-end: 1px solid var(--_border-color);
    grid-area: header;
    }
    .ui-footer {
    border-block-start: 1px solid var(--_border-color);
    grid-area: footer;
    }
    /* Remove padding on the input when affixes are next to it */
    .ui-field:has(> .ui-prefix) :where(input, textarea, select) {
    padding-inline-start: 0;
    }
    .ui-field:has(> .ui-suffix) :where(input, textarea, select) {
    padding-inline-end: 0;
    }
    /* Textarea - keep affix aligned with the first line */
    &.ui-textarea .ui-field {
    .ui-prefix,
    .ui-suffix {
    align-self: start;
    padding-block: var(--_field-padding-block);
    }
    }
    /* Required/Invalid */
    &:has(:invalid) {
    .ui-label:after {
    color: var(--red);
    content: "*";
    margin: -0.25em auto auto 0.25em;
    }
    }
    /* File */
    &:has(input[type="file"]) {
    cursor: pointer;
    input {
    align-self: flex-start;
    block-size: var(--_height);
    box-shadow: none;
    color: var(--text-primary);
    cursor: inherit;
    max-inline-size: 100%;
    padding: 0;
    transition: font-size 0.2s var(--ease-3);
    &::-webkit-file-upload-button,
    &::file-selector-button {
    background-color: var(--surface-tonal);
    block-size: calc(100% - var(--size-2) * 2);
    border: none;
    border-radius: var(--field-border-radius);
    cursor: pointer;
    margin-block-start: var(--size-2);
    margin-inline-end: 1ex;
    margin-inline-start: var(--size-2);
    }
    }
    /* Variants */
    &.ui-filled {
    input {
    &::-webkit-file-upload-button,
    &::file-selector-button {
    background-color: var(--surface-default);
    block-size: calc(100% - var(--size-2) * 2);
    border-radius: var(--field-border-radius);
    cursor: pointer;
    margin-block-start: var(--size-2);
    }
    }
    }
    /* Sizes */
    &.ui-small {
    input {
    font-size: var(--font-size-05);
    &::-webkit-file-upload-button,
    &::file-selector-button {
    block-size: calc(100% - var(--size-2));
    margin-block-start: var(--size-1);
    }
    }
    }
    }
    /* Autosuggest */
    &:has(input[list]) {
    .ui-label {
    /* Make sure chevron is visible */
    inline-size: calc(100% - var(--size-6));
    }
    }
    /* Select */
    &:has(select) {
    .ui-label {
    /* Make sure chevron is visible */
    inline-size: calc(100% - var(--size-6));
    }
    }
    /* Customizable Select */
    &:has(select button) {
    select {
    padding: 0;
    button {
    outline: 0;
    padding: var(--_field-padding-block) var(--size-8)
    var(--_field-padding-block) var(--_field-padding-inline);
    }
    }
    }
    /* Non-customizable Select */
    &:has(select):not(:has(button)) {
    select {
    padding: var(--_field-padding-block) var(--size-8)
    var(--_field-padding-block) var(--_field-padding-inline);
    }
    }
    /* Input - color */
    &:has(input[type="color"]) {
    .ui-field {
    block-size: var(--_height);
    inline-size: var(--_height);
    min-block-size: 0;
    overflow: hidden;
    }
    input {
    appearance: none;
    background: none;
    block-size: var(--_height);
    inline-size: var(--_height);
    overflow: hidden;
    padding: 0;
    &::-webkit-color-swatch {
    border: none;
    }
    &::-webkit-color-swatch-wrapper {
    padding: 0;
    }
    }
    }
    /* Element states */
    &:hover {
    &:not([data-invalid]) {
    .ui-field {
    border-color: var(--text-primary);
    }
    }
    }
    &:focus-within {
    .ui-field {
    border-color: var(--_accent-color);
    outline-offset: -2px;
    }
    }
    /* Label */
    .ui-label {
    color: var(--text-primary);
    font-size: var(--font-size-05);
    font-weight: 600;
    grid-column: 1/-1;
    grid-row: 1;
    padding-inline: 0 1ex;
    }
    :where(.ui-start-text) {
    color: var(--_end-text-color);
    font-size: var(--font-size-0);
    grid-column: 1/-1;
    grid-row: 2;
    line-height: 1.5;
    }
    &:not(.ui-spread):has(> .ui-start-text) {
    .ui-field {
    grid-row: 3;
    }
    :where(.ui-end-text) {
    grid-row: 4;
    }
    }
    /* End text */
    :where(.ui-end-text) {
    color: var(--_end-text-color);
    font-size: var(--font-size-0);
    grid-column: 1/-1;
    grid-row: 3;
    line-height: 1.5;
    }
    /* Auto-fit */
    &.ui-auto-fit {
    inline-size: auto;
    .ui-field {
    inline-size: auto;
    }
    :where(& input, & textarea) {
    field-sizing: content;
    inline-size: auto;
    min-inline-size: 25ch;
    }
    }
    /* Validation */
    &[data-invalid],
    &:has(:user-invalid) {
    --_accent-color: var(--color-9);
    --_border-color: var(--color-9);
    --_filled-border-color: var(--color-9);
    --_label-color: var(--color-9);
    --_end-text-color: var(--color-9);
    }
    /*
    * Variant: Filled
    */
    &.ui-filled {
    --_bg-color: var(--surface-tonal);
    &:not(:has([disabled], :has(input[type="color"]))) {
    /* Hover */
    &:hover {
    --_bg-color: light-dark(
    oklch(from var(--surface-tonal) calc(l * 0.93) c h),
    oklch(from var(--surface-tonal) calc(l * 1.1) c h)
    );
    }
    }
    }
    /* Disabled */
    &:where(:has([disabled])) {
    cursor: not-allowed;
    opacity: 0.64;
    user-select: none;
    &::before {
    display: none;
    }
    .ui-field {
    cursor: not-allowed;
    }
    :where(input, textarea, select) {
    cursor: not-allowed;
    * {
    pointer-events: none;
    }
    }
    .ui-label,
    .ui-start-text,
    .ui-end-text {
    cursor: not-allowed;
    }
    }
    /* Read-only */
    &:where(:has([readonly])) {
    &::before {
    display: none;
    }
    :where(input, textarea, select) {
    cursor: not-allowed;
    * {
    pointer-events: none;
    }
    }
    }
    /* Sizes */
    &.ui-small {
    --_field-padding-block: var(--size-2);
    --_height: var(--field-size-small);
    &:has(input[type="color"]) {
    .ui-label {
    line-height: 1.5;
    }
    }
    }
    /* Orientation */
    &.ui-spread {
    align-items: start;
    column-gap: var(--size-4);
    container-type: inline-size;
    grid-template-columns: 1fr auto;
    .ui-label {
    font-weight: 600;
    grid-column: 1;
    grid-row: 1;
    }
    .ui-start-text {
    color: var(--_end-text-color);
    font-size: var(--font-size-0);
    grid-column: 1;
    grid-row: 2;
    line-height: 1.5;
    }
    .ui-field {
    align-self: start;
    grid-column: 2;
    grid-row: 1 / span 2;
    }
    .ui-field:has(textarea) {
    grid-row: 1 / span 3;
    + .ui-end-text {
    grid-row: 4;
    }
    }
    :where(textarea) {
    min-inline-size: 30ch;
    }
    :where(.ui-end-text) {
    grid-column: 2;
    text-align: end;
    }
    /* Collapse to column on narrow containers */
    @container (width < 400px) {
    column-gap: 0;
    grid-template-columns: 1fr;
    .ui-label {
    grid-column: 1;
    grid-row: 1;
    }
    .ui-start-text {
    grid-column: 1;
    grid-row: 2;
    }
    .ui-field,
    .ui-field:has(textarea) {
    grid-column: 1;
    grid-row: 3;
    }
    :where(.ui-end-text) {
    grid-column: 1;
    grid-row: 4;
    text-align: start;
    }
    }
    }
    }
    }
    @layer components.extended {
    :where(.ui-textarea) {
    textarea {
    block-size: auto;
    field-sizing: content;
    min-block-size: calc(
    var(--_field-padding-block) * 2 + (var(--border-width) * 2) + 3lh
    );
    resize: vertical;
    }
    /* Size */
    &.ui-small {
    textarea {
    min-block-size: var(--size-9);
    }
    }
    /* Auto-fit */
    &.ui-auto-fit {
    textarea {
    resize: both;
    }
    }
    }
    }