Browse docs
Browse docs
A multi-line input. The vertical counterpart of <TextField> — same bridge wiring, same label/helper/error chrome, plus rows and resize controls. Use it for any free-text field longer than a typical single-line value (notes, descriptions, paste areas).
import { Textarea } from '@dashforge/tw';
<Textarea name="bio" label="Tell us about yourself" rows={4} />import { DashForm } from '@dashforge/forms';
import { Textarea, Button } from '@dashforge/tw';
<DashForm onSubmit={onSubmit}>
<Textarea
name="message"
label="Your message"
rows={6}
required
rules={{ minLength: { value: 20, message: 'At least 20 characters' } }}
/>
<Button type="submit" color="primary">Send</Button>
</DashForm>Standalone:
const [value, setValue] = useState('');
<Textarea
name="notes"
value={value}
onChange={(e) => setValue(e.target.value)}
/>Markdown is supported.
import { Textarea } from '@dashforge/tw';
<Textarea
name="bio"
label="Short bio"
placeholder="A few sentences about yourself…"
rows={4}
helperText="Markdown is supported."
/><Textarea size="sm" rows={2} /> {/* compact, 2 lines visible */}
<Textarea size="md" rows={4} /> {/* default */}
<Textarea size="lg" rows={8} /> {/* spacious, hero forms */}rows controls the visible line count (= <textarea rows> HTML attribute). Default is 3.
import { Textarea, Stack } from '@dashforge/tw';
<Stack gap={3}>
<Textarea
name="vertical"
label='resize="vertical" (default)'
rows={3}
defaultValue="Drag the bottom-right corner to grow taller."
/>
<Textarea
name="none"
label='resize="none"'
rows={3}
resize="none"
defaultValue="Locked size — no drag handle."
/>
</Stack><Textarea resize="vertical" /> {/* default — user can drag-resize bottom edge */}
<Textarea resize="none" /> {/* locked at `rows` size */}
<Textarea resize="both" /> {/* horizontal + vertical (rare) */}
<Textarea resize="horizontal" /> {/* rare */}Vertical-only is the sane default. none for fixed-frame layouts (cards with strict heights).
<Textarea
name="bio"
label="Bio"
helperText="Markdown supported. Keep it under 280 characters for the preview to look right."
rows={4}
/>
<Textarea
name="bio"
label="Bio"
error
helperText="Bio is required to publish."
/><RadioGroup name="contactPref" options={[{value:'email'},{value:'detail'}]} />
<Textarea
name="contactDetail"
label="Tell us more"
visibleWhen={(engine) => engine.getNode('contactPref')?.value === 'detail'}
rows={5}
required
/><Textarea
name="adminNote"
label="Internal note"
rows={3}
access={{ requires: 'workspace.admin', when: 'denied:readonly' }}
/>Three when modes ('denied:hide' | 'denied:disable' | 'denied:readonly') — same semantics as TextField.
Configure <Textarea> defaults application-wide.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
components: {
Textarea: {
defaults: { size: 'md', layout: 'stacked', resize: 'vertical', fullWidth: true },
},
},
});Configurable axes (TextareaVariantProps):
| Axis | Type | Notes |
|---|---|---|
size | 'sm' | 'md' | 'lg' | Wrapper padding + font-size. |
layout | 'stacked' | 'inline' | Label position — above (stacked) or left of the wrapper (inline). |
fullWidth | boolean | Stretch root + wrapper to container width. |
resize | 'none' | 'vertical' | 'horizontal' | 'both' | User-drag resize behavior via the native resize CSS. |
Non-visual axes (name, rules, label, helperText, error, disabled, access, visibleWhen, event handlers, rows) are not theme-configurable — they carry per-instance semantics.
Precedence chain (lowest → highest):
defaultVariants from the internal textareaVariants recipe.theme.components.Textarea.defaults (application-wide).<Textarea size="lg" resize="none" />) — wins over theme.sx — appended to the root and wins over conflicting classes via tailwind-merge.TypeScript: theme.components.Textarea.defaults autocompletes to the four axes above.
Reactivity: useComponentDefaults('Textarea') subscribes to the theme store — patchTheme at runtime re-renders every mounted <Textarea> inheriting the changed axis.
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Bridge field name (required when used inside DashFormProvider). |
access | AccessRequirement | — | RBAC access requirement (combines with explicit disabled). |
defaultValue | string | — | Default value (uncontrolled, standalone mode only). |
disabled | boolean | false | Disables the textarea — ORed with RBAC denied:disable. |
error | boolean | false | Explicit error semaphore. Overrides the bridge's auto-detected error. |
fullWidth | boolean | false | Stretch the root wrapper + textarea to the container's width. |
helperText | ReactNode | — | Helper line below the textarea. Auto-replaced by bridge error when invalid. |
label | ReactNode | — | Visible label above (or left of, per layout) the textarea. |
layout | 'stacked' | 'inline' | 'stacked' | Label placement — 'stacked' (above the textarea) or 'inline' (left of the textarea). |
onBlur | FocusEventHandler<HTMLTextAreaElement> | — | User-supplied blur handler — fires after bridge onBlur. |
onChange | ChangeEventHandler<HTMLTextAreaElement> | — | User-supplied change handler — fires after bridge update. |
required | boolean | false | Renders the required * marker + sets the native required attribute. |
resize | 'none' | 'horizontal' | 'vertical' | 'both' | 'vertical' | User-drag resize behavior via the native resize CSS property. |
rows | number | — | Minimum visible row count for the textarea. Default: 3. |
rules | unknown | — | RHF validation rules — opaque, forwarded to the bridge. |
size | 'md' | 'sm' | 'lg' | 'md' | Density tier — drives wrapper padding + font-size. |
slotProps | TextareaSlotProps | — | Per-slot className overrides. |
sx | string | — | Root className shortcut (cn'd with the variant root class). |
value | string | — | Controlled value (form mode reads from the bridge if omitted). |
visibleWhen | (engine: Engine) => boolean | — | Engine predicate — textarea not rendered when it returns false. |
...rest | Omit< TextareaHTMLAttributes<HTMLTextAreaElement>, 'name' | 'size' | 'onChange' | 'onBlur' | 'value' | 'defaultValue' > | — | Additional native attributes forwarded via Omit< TextareaHTMLAttributes<HTMLTextAreaElement>, 'name' | 'size' | 'onChange' | 'onBlur' | 'value' | 'defaultValue' >. |
<Textarea> is a compound component with 7 named slots. Each slot accepts a { className?: string } override via slotProps:
| Slot | Purpose |
|---|---|
root | Outer flex wrapper (label + wrapper + helper/error). |
label | The <label> element. |
requiredMark | The red * next to the label when required. |
inputWrapper | The bordered surface around the textarea. |
input | The <textarea> element itself. |
helperText | Helper text line, when not in error state. |
errorText | Helper text line, when in error state (distinct styling). |
For root-level class overrides that don't need a specific slot, use sx — it's appended to the root and wins over conflicting classes via tailwind-merge. Use slotProps when you need to reach a specific inner element (e.g. the bordered inputWrapper, or errorText color).
<textarea> under the hood — maxLength, cols, wrap, spellCheck etc. pass through via rest props.useResizeObserver-style hook). For autosizing, set rows to a sensible cap and pair with maxLength; or wrap in a custom container.resize="vertical" (default) follows browser conventions and avoids the "stretchy" feeling of both that confuses non-technical users.