Files
Htmx/docs/Components/Textarea.md
T
2026-05-04 19:57:48 +05:00

6.6 KiB

Textarea

A styled multi-line text input with optional label, description, default value, and HTMX attributes.


HTML structure

div.flex.flex-col.gap-1.5
  label[for={id}].text-sm.font-medium        ← omitted when label is empty
    {label}
  textarea[id, name, placeholder, rows, class, $$HxAttrs$$]
    {defaultValue}
  p.text-sm.text-muted-foreground            ← omitted when description is empty
    {description}

CSS mechanics

Class Effect
flex min-h-[80px] w-full rounded-md border border-input bg-background Full-width field with minimum height
px-3 py-2 text-sm Inner padding and text size
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring Keyboard focus ring
disabled:cursor-not-allowed disabled:opacity-50 Disabled state
placeholder:text-muted-foreground Muted placeholder text
resize-y Allows vertical resize only

Constructor signature

public Textarea(
    string id,
    string name         = "",
    string placeholder  = "",
    string label        = "",
    string description  = "",
    string defaultValue = "",
    string extraClasses = "",
    string hxAttrs      = "",
    int    rows         = 3)
Parameter Description
id Element id and label for target
name Form field name
placeholder Placeholder text
label Optional visible label
description Optional helper text below the field
defaultValue Pre-filled content of the textarea
extraClasses Additional Tailwind classes on the textarea
hxAttrs Verbatim HTMX / data attributes
rows Number of visible rows (default: 3)

Usage examples

Comment field

new Textarea(
    id:          "comment",
    name:        "comment",
    placeholder: "Write a comment…",
    label:       "Comment",
    rows:        5)

Bio field with default value

new Textarea(
    id:           "bio",
    name:         "bio",
    label:        "Bio",
    description:  "Tell us about yourself (max 280 characters)",
    defaultValue: user.Bio ?? "")

Auto-expand with HTMX

new Textarea(
    id:      "notes",
    name:    "notes",
    label:   "Notes",
    rows:    3,
    hxAttrs: """oninput="this.style.height=''; this.style.height=this.scrollHeight+'px'"""")

Auto-save on input

new Textarea(
    id:      "draft",
    name:    "content",
    label:   "Draft",
    hxAttrs: """hx-post="/drafts/save" hx-trigger="keyup changed delay:500ms" hx-include="[name='content']"""")

Reading in a form handler

public record Command([property: FromForm] string Comment);

// command.Comment contains the textarea value

Tips and tricks

  • HTML-encode the defaultValue if it contains user-supplied content — it is placed directly inside the <textarea> element.
  • rows controls the initial visible height but the user can resize vertically. For a fixed-height textarea, add resize-none in extraClasses.
  • For a character counter, add maxlength via hxAttrs and pair with a small JS snippet or a sibling <span> updated on input.
  • placeholder text is not submitted — always use defaultValue for edit forms where existing content should be pre-filled.
  • placeholder text is not submitted — always use defaultValue for edit forms where existing content should be pre-filled.

Complete page example

Templates/FeedbackPage.htmx

<div class="max-w-lg mx-auto py-10">
  <h1 class="text-2xl font-bold mb-2">Send feedback</h1>
  <p class="text-sm text-muted-foreground mb-6">We read every message.</p>
  <form method="post" action="/feedback">
  $$AntiforgeryToken$$
  <div class="space-y-5 mb-6">
    $$SubjectInput$$
    $$MessageArea$$
  </div>
  $$SubmitBtn$$
  </form>
  $$SuccessAlert$$
</div>

Templates/FeedbackPage.htmx.cs

namespace Htmx.ApiDemo.Templates;

public sealed class FeedbackPage : FeedbackPageBase
{
  private readonly IHtmxComponent _subject;
  private readonly IHtmxComponent _message;
  private readonly IHtmxComponent _submit;
  private readonly IHtmxComponent _success;
  private readonly byte[] _afToken;

  public FeedbackPage(
    IAntiforgery af,
    HttpContext ctx,
    string subjectError  = "",
    string messageError  = "",
    bool   submitted     = false)
  {
    var tokens = af.GetAndStoreTokens(ctx);
    _afToken = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".ToUtf8Bytes();

    _subject = new Components.Input(
      id:          "subject",
      name:        "subject",
      label:       "Subject",
      placeholder: "What's on your mind?",
      errorMessage: subjectError);

    _message = new Components.Textarea(
      id:           "message",
      name:         "message",
      label:        "Message",
      rows:         6,
      placeholder:  "Describe your feedback in detail…",
      errorMessage: messageError);

    _submit  = new Components.Button("Send feedback", type: "submit");
    _success = submitted
      ? new Components.Alert(title: "Thank you!", description: "Your feedback has been received.")
      : HtmxEmpty.Instance;
  }

  protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken);
  protected override void RenderSubjectInput(HtmxRenderContext ctx)     => _subject.Render(ctx.Next());
  protected override void RenderMessageArea(HtmxRenderContext ctx)      => _message.Render(ctx.Next());
  protected override void RenderSubmitBtn(HtmxRenderContext ctx)        => _submit.Render(ctx.Next());
  protected override void RenderSuccessAlert(HtmxRenderContext ctx)     => _success.Render(ctx.Next());
}

POST handler

[Handler]
[MapPost("/feedback")]
public static partial class PostFeedbackHandler
{
  public record Command(
    [property: FromForm] string Subject,
    [property: FromForm] string Message);

  private static Task<IResult> HandleAsync(
    [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct)
  {
    if (string.IsNullOrWhiteSpace(cmd.Subject))
      return ctx.WriteHtmxPage(
        new FeedbackPage(af, ctx, subjectError: "Subject is required."), title: "Feedback");

    if (string.IsNullOrWhiteSpace(cmd.Message))
      return ctx.WriteHtmxPage(
        new FeedbackPage(af, ctx, messageError: "Message is required."), title: "Feedback");

    // Persist feedback…
    return ctx.WriteHtmxPage(
      new FeedbackPage(af, ctx, submitted: true), title: "Feedback");
  }
}

AppJsonSerializerContext.cs

[JsonSerializable(typeof(PostFeedbackHandler.Command), TypeInfoPropertyName = "FeedbackCommand")]