Kosmesis
Components

Textarea

A multi-line text input. Purely presentational — no Morphos primitive (Radix has none either).

Installation

npx kosmesis add textarea
pnpm dlx kosmesis add textarea
yarn dlx kosmesis add textarea
bunx kosmesis add textarea

Copy and paste the following code into your project.

textarea.tsx
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/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

Copy and paste the following code into your project.

textarea.tsx
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

PropTypeDefault
value / defaultValuestring
rowsnumber3
disabled / readonly / requiredboolean
onInput / onChange(value: string, event: Event) => void

On this page