apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Textarea
Variant↳ other

Inputs

Textarea

Multi-line text input. Shares the form-control surface with Input (same variants, sizes, colors, focus-ring story) and adds auto-resize + an optional character counter. The DRY proof of the Phase 7 shared layer.

Overview

<Textarea /> is the canonical multi-line text-input primitive. It pairs with <label> for the accessible name and shares the same form-control surface as <Input /> — variants, sizes, colors, focus ring, and invalid state — extended for multi-line use with auto-resize, character counts, and manual resize affordances.

Overview — placeholder, content, and validation

Loading preview…
Overview.tsx

Variants

Same four "border + background" stories as Input — by design, the two components are visually indistinguishable in the resting state of a form. Adding a variant to Input adds it to Textarea (the type is a literal alias, and the recipe imports the same compound rows).

  • outline — 1px border, paper background. Default; safest on any surface.
  • solid — filled tint, no visible border. Best when the parent is already bg.paper.
  • ghost — invisible at rest; gains a tinted border + faint bg on hover / focus.
  • underline — Material-style single bottom rule, dense forms.

Every variant works with every color (7 colors). The focus ring + focused border use the active color; the body border stays neutral at rest so colored textareas don't shout for attention.

Anatomy

text
[ <textarea/> ]                  ← inner element, padding + leading-relaxed + resize
[             counter (optional) ]   ← absolute end-bottom, pointer-events-none
[ <textarea/> ]                  ← inner element, padding + leading-relaxed + resize
[             counter (optional) ]   ← absolute end-bottom, pointer-events-none

Multi-line-specific concerns

  • autoResize (true by default) grows the textarea with content, clamped between minRows and maxRows. Once content exceeds maxRows, an internal scrollbar takes over.
  • resize ('vertical' by default) controls the native CSS resize property — the browser-rendered corner grip. Works alongside autoResize: a user can still drag below the auto-resize ceiling unless resize is 'none'.
  • showCount (paired with maxLength) renders a small bottom-end counter. When the limit is hit, data-at-limit="true" flips the counter text to the danger token — consumers can CSS-target this for custom animation.

Examples

Basic

Loading preview…
Basic.tsx

Variants

Loading preview…
Variants.tsx

Sizes

Loading preview…
Sizes.tsx

AutoResize

Loading preview…
AutoResize.tsx

WithCount

Loading preview…
WithCount.tsx

ResizeModes

Loading preview…
ResizeModes.tsx

Invalid

Loading preview…
Invalid.tsx

Disabled

Loading preview…
Disabled.tsx

Controlled

Loading preview…
Controlled.tsx

Props

PropTypeDefaultDescription
autoResizebooleantrueGrow the textarea with its content. Off-the-shelf modern UX — opt out only for fixed-height layouts where the surrounding chrome shouldn't shift.
colorResponsiveValue<InputColor>'primary'Accent for the focus ring + focused border.
fullWidthResponsiveValue<boolean>trueStretch to fill the parent's inline-size.
invalidboolean—Visual + `aria-invalid` invalid state. Wins over the active `color` for border + ring.
maxRowsnumber—Ceiling for `autoResize`. Once content exceeds this many lines the textarea stops growing and an internal scrollbar takes over. Omit for no ceiling.
minRowsnumber—Floor for `autoResize`. The textarea won't shrink below this many lines regardless of how empty it gets. Defaults to `rows`.
resizeenum'vertical'Manual resize affordance. The native CSS `resize` property: `'none'` hides the corner grip, `'vertical'` (default) is the modal expectation, `'horizontal'` is unusual, `'both'` is the browser default.
rowsnumber3Initial visible row count. Used as the textarea's natural height before `autoResize` kicks in. When `autoResize` is off this is the static height.
showCountboolean—Render the bottom-end character counter. Pairs naturally with `maxLength` — when both are present the counter reads `current / max` and flips `data-at-limit` once the cap is hit. Without `maxLength`, just the current length is shown.
sizeResponsiveValue<TextareaSize>'md'Vertical padding + font-size scale.
sxSx—Theme-aware inline style object (resolves palette / spacing / radius tokens to CSS vars).
variantResponsiveValue<InputVariant>'outline'Border + background story.

Accessibility

  • Renders a native <textarea> — keyboard interaction, IME composition, and form submission all use the platform's built-in behaviors.
  • Labels: Textarea never auto-labels. Wrap with <label htmlFor> (or the future <Field>), pass aria-label, or pass aria-labelledby. A dev-mode warning fires when none of those are present — [apx-ds][TEXTAREA_NO_LABEL].
  • Invalid state: invalid={true} sets aria-invalid="true" on the textarea and a matching data-invalid on the wrapper. The danger border + ring come from the same attribute selector Input uses, so a future <Field invalid> propagates without prop-drilling.
  • aria-describedby is preserved as-is. The character counter is aria-hidden="true" (the count is decoration — the textarea itself announces value changes natively). Consumers who want screen-reader announcement of the count should render a <span aria-live="polite"> separately and wire it via aria-describedby.
  • Disabled vs read-only: disabled is unreachable by tab; readOnly is reachable but uneditable.
  • Focus ring lives on the wrapper (focus-within:ring-*) so the counter footer is inside the same visual frame as the textarea.

Theming

Both root (wrapper), textarea (inner element), and count (counter footer) are addressable slots. Tweak the global theme to remix the whole library at once, or scope overrides to Textarea specifically:

tsx
<ThemeProvider
  theme={defineTheme({
    components: {
      Textarea: {
        defaultProps: { autoResize: false, rows: 6 },
        styleOverrides: {
          root: 'shadow-xs',
          textarea: 'font-mono leading-snug',
          count: 'text-fg',
        },
      },
    },
  })}
>
  {children}
</ThemeProvider>
<ThemeProvider
  theme={defineTheme({
    components: {
      Textarea: {
        defaultProps: { autoResize: false, rows: 6 },
        styleOverrides: {
          root: 'shadow-xs',
          textarea: 'font-mono leading-snug',
          count: 'text-fg',
        },
      },
    },
  })}
>
  {children}
</ThemeProvider>

Instance-level overrides win via tailwind-merge:

tsx
<Textarea className="font-mono" sx={{ radius: 'xl' }} style={{ minHeight: 240 }} />
<Textarea className="font-mono" sx={{ radius: 'xl' }} style={{ minHeight: 240 }} />

Do / Don't

  • Do pair every Textarea with a visible <label>. Placeholders are not labels.
  • Do use maxLength + showCount when there's a hard cap so users know in advance.
  • Don't use disabled for "not editable right now" — readOnly keeps the field reachable.
  • Don't turn off autoResize unless you have a strong layout reason; modern forms expect the field to grow with content.