Components
Textarea
A multi-line text input. Purely presentational — no Morphos primitive (Radix has none either).
Installation
npx kosmesis add textareapnpm dlx kosmesis add textareayarn dlx kosmesis add textareabunx kosmesis add textareaCopy and paste the following code into your project.
import { StatefulComponent } from "@praxisjs/core";import { Component, Emit, Prop } from "@praxisjs/decorators";import { cn } from "@/lib/utils";export interface TextareaProps { value?: string; defaultValue?: string; placeholder?: string; disabled?: boolean; readonly?: boolean; required?: boolean; rows?: number; name?: string; class?: string; id?: string; onInput?: (value: string, event: Event) => void; onChange?: (value: string, event: Event) => void;}/** Purely presentational — no Morphos equivalent (Radix has no textarea primitive either). */@Component()export class Textarea extends StatefulComponent { @Prop() value?: string; @Prop() defaultValue?: string; @Prop() placeholder?: string; @Prop() disabled?: boolean; @Prop() readonly?: boolean; @Prop() required?: boolean; @Prop() rows = 3; @Prop() name?: string; @Prop() class?: string; @Prop() id?: string; @Prop() onInput?: TextareaProps["onInput"]; @Prop() onChange?: TextareaProps["onChange"]; private get _value(): string | undefined { return this.value ?? this.defaultValue; } @Emit("onInput") private _emitInput(value: string, event: Event): void { void value; void event; } @Emit("onChange") private _emitChange(value: string, event: Event): void { void value; void event; } private readonly _handleInput = (event: Event) => { const target = event.target as HTMLTextAreaElement; this._emitInput(target.value, event); }; private readonly _handleChange = (event: Event) => { const target = event.target as HTMLTextAreaElement; this._emitChange(target.value, event); }; render() { return ( <textarea id={this.id} value={() => this._value} placeholder={this.placeholder} disabled={this.disabled} readOnly={this.readonly} required={this.required} rows={this.rows} name={this.name} class={cn( "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", this.class, )} onInput={this._handleInput} onChange={this._handleChange} /> ); }}Install the following dependencies:
npm install @praxisjs/csspnpm add @praxisjs/cssyarn add @praxisjs/cssbun add @praxisjs/cssCopy and paste the following code into your project.
import { StatefulComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component, Emit, Prop } from "@praxisjs/decorators";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class TextareaStyles extends Stylesheet { // Note: the Tailwind version also sets `field-sizing: content` (auto-grow to fit content) — // omitted here since it isn't in csstype's typed CSS properties yet; the textarea still works // fine, it just doesn't auto-grow without a manual `rows` bump or your own resize handling. $root = this.css({ display: "flex", minHeight: "4rem", width: "100%", borderRadius: `calc(${t.radius} - 2px)`, border: `1px solid ${t.input}`, backgroundColor: "transparent", padding: "0.5rem 0.75rem", fontSize: "0.875rem", boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", outline: "none", transition: "color 120ms ease, box-shadow 120ms ease", }) .placeholder({ color: t.mutedForeground }) .disabled({ cursor: "not-allowed", opacity: 0.5 }) .focusVisible({ borderColor: t.ring, boxShadow: `0 0 0 3px color-mix(in oklab, ${t.ring} 50%, transparent)` });}export interface TextareaProps { value?: string; defaultValue?: string; placeholder?: string; disabled?: boolean; readonly?: boolean; required?: boolean; rows?: number; name?: string; class?: string; id?: string; onInput?: (value: string, event: Event) => void; onChange?: (value: string, event: Event) => void;}/** Purely presentational — no Morphos equivalent (Radix has no textarea primitive either). */@Component()export class Textarea extends StatefulComponent { @Prop() value?: string; @Prop() defaultValue?: string; @Prop() placeholder?: string; @Prop() disabled?: boolean; @Prop() readonly?: boolean; @Prop() required?: boolean; @Prop() rows = 3; @Prop() name?: string; @Prop() class?: string; @Prop() id?: string; @Prop() onInput?: TextareaProps["onInput"]; @Prop() onChange?: TextareaProps["onChange"]; @Styled(TextareaStyles) $s!: TextareaStyles; private get _value(): string | undefined { return this.value ?? this.defaultValue; } @Emit("onInput") private _emitInput(value: string, event: Event): void { void value; void event; } @Emit("onChange") private _emitChange(value: string, event: Event): void { void value; void event; } private readonly _handleInput = (event: Event) => { const target = event.target as HTMLTextAreaElement; this._emitInput(target.value, event); }; private readonly _handleChange = (event: Event) => { const target = event.target as HTMLTextAreaElement; this._emitChange(target.value, event); }; render() { return ( <textarea id={this.id} value={() => this._value} placeholder={this.placeholder} disabled={this.disabled} readOnly={this.readonly} required={this.required} rows={this.rows} name={this.name} class={cx(this.$s.$root, this.class)} onInput={this._handleInput} onChange={this._handleChange} /> ); }}Examples
Usage
import { Textarea } from "@/components/ui/textarea";
<Textarea placeholder="Type your message here." rows={4} />Props
| Prop | Type | Default |
|---|---|---|
value / defaultValue | string | — |
rows | number | 3 |
disabled / readonly / required | boolean | — |
onInput / onChange | (value: string, event: Event) => void | — |