Zum Inhalt springen
UI Kit
Accent

Textbereich

Mehrzeiliger Text, der mit dem Inhalt wächst, mit Zähler, Fußzeile und Fehler als Text.

Eine von 9 Components in Forms und Inputs

2 Variants

Variants

Zustände

Wächst beim Tippen, der Zähler wird kurz vor der Grenze kräftig und darüber rot, und die Box schüttelt einmal, wenn du darüber gehst.

Eingabebox

Eine Fußzeile mit Werkzeugen und Sendeknopf, wie bei Kommentaren und Chats.

Details

Was Textbereich mitbringt, aus Registry und Quellcode gelesen.

Bereich
Elements
Variants
2
Version
1.0.0 · seit 1.0.0
Datei
components/ui/textarea.tsx
Bringt mit
utilskitinput
npm-Pakete
Keine
Sprachen
Englisch und Deutsch, 2 eigene Texte
Studio
Im Code anpassen

Props

Die Schnittstelle TextareaProps aus components/ui/textarea.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von FieldOwnProps, Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength'> an.

  • autoResizeoptional
    boolean

    Grows with its content between minRows and maxRows (CSS field-sizing, falls back to rows)

  • minRowsoptional
    number
  • maxRowsoptional
    number
  • maxLengthoptional
    number

    Shows a counter. Going over is allowed while typing and shown as an error, so nothing gets cut off silently.

  • footeroptional
    React.ReactNode

    Something in the footer row, e.g. formatting hints or an attach button

  • invalidoptional
    boolean
  • refoptional
    React.Ref<HTMLTextAreaElement>

Eingebaute Texte

Diese Texte schreibt Textbereich selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.

Alle 2 Texte anzeigen
leftEnglisch characters leftDeutsch Zeichen übrig
overEnglisch characters too manyDeutsch Zeichen zu viel

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Setzt aria-describedby und aria-invalid, damit Screenreader Zweck und Zustand ansagen.
  • Ergänzt Text für Screenreader, wo Icons und Grafiken allein stehen.
  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Ohne Animationen, sie steht sofort ruhig da.

Quellcode

Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.

Datei anzeigen, 86 Zeilen
components/ui/textarea.tsx
'use client';

import { useRef, useState } from 'react';
import { cn, useLocale, useTexts } from '@/lib/kit';
import { controlBox, useControl, useShake, withField, type FieldOwnProps } from '@/components/ui/input';

const TEXTS = {
  en: { left: 'characters left', over: 'characters too many' },
  de: { left: 'Zeichen übrig', over: 'Zeichen zu viel' },
};

export interface TextareaProps extends FieldOwnProps, Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength'> {
  /** Grows with its content between minRows and maxRows (CSS field-sizing, falls back to rows) */
  autoResize?: boolean;
  minRows?: number;
  maxRows?: number;
  /** Shows a counter. Going over is allowed while typing and shown as an error, so nothing gets cut off silently. */
  maxLength?: number;
  /** Something in the footer row, e.g. formatting hints or an attach button */
  footer?: React.ReactNode;
  invalid?: boolean;
  ref?: React.Ref<HTMLTextAreaElement>;
  labels?: Partial<(typeof TEXTS)['en']>;
  className?: string;
}

function TextareaBox({ autoResize = true, minRows = 3, maxRows = 10, maxLength, footer, invalid, labels, className, value, defaultValue, onChange, ref, ...rest }: Omit<TextareaProps, keyof FieldOwnProps>) {
  const t = useTexts(TEXTS, labels);
  const locale = useLocale();
  const c = useControl({ ...rest, invalid });
  const [inner, setInner] = useState(String(defaultValue ?? ''));
  const text = value !== undefined ? String(value) : inner;
  const over = maxLength !== undefined && text.length > maxLength;
  const left = maxLength !== undefined ? maxLength - text.length : 0;
  const fmt = (n: number) => n.toLocaleString(locale === 'de' ? 'de-DE' : 'en-US');
  const showFooter = maxLength !== undefined || footer;
  const boxRef = useRef<HTMLDivElement>(null);
  // One shake when the text goes over the limit or the field turns invalid
  useShake(boxRef, c.invalid || over);
  return (
    <div ref={boxRef} data-invalid={c.invalid || over || undefined} data-disabled={c.disabled || undefined} className={cn('flex w-full flex-col overflow-hidden rounded-lg', controlBox, className)}>
      <textarea
        {...rest}
        ref={ref}
        id={c.id}
        value={value}
        defaultValue={value === undefined ? defaultValue : undefined}
        onChange={(e) => {
          setInner(e.target.value);
          onChange?.(e);
        }}
        disabled={c.disabled}
        required={c.required}
        aria-invalid={c.invalid || over || undefined}
        aria-describedby={c['aria-describedby']}
        rows={minRows}
        style={{ minHeight: `calc(${minRows} * 1.5em + 1.25rem)`, maxHeight: `calc(${maxRows} * 1.5em + 1.25rem)` }}
        className={cn(
          'w-full resize-none bg-transparent px-3.5 py-2.5 text-base leading-[1.5] text-inherit outline-none placeholder:text-subtle-foreground disabled:cursor-not-allowed sm:text-sm sm:leading-[1.5]',
          autoResize ? '[field-sizing:content]' : 'resize-y',
          'kit-scrollbar',
        )}
      />
      {showFooter && (
        <div className="flex min-h-10 items-center justify-between gap-3 px-2 pb-2 ps-3.5">
          <div className="flex min-w-0 flex-1 items-center gap-1">{footer}</div>
          {maxLength !== undefined && (
            <p className={cn('ms-auto shrink-0 text-xs tabular-nums transition-colors duration-300', over ? 'font-medium text-destructive' : left <= maxLength * 0.1 ? 'text-foreground' : 'text-subtle-foreground')}>
              <span aria-hidden>{over ? `−${fmt(-left)}` : `${fmt(text.length)} / ${fmt(maxLength)}`}</span>
              <span className="sr-only">{over ? `${fmt(-left)} ${t.over}` : `${fmt(left)} ${t.left}`}</span>
            </p>
          )}
        </div>
      )}
    </div>
  );
}

/**
 * Multi-line text that grows with its content, with an optional counter and footer.
 *   <Textarea label="Message" maxLength={500} hint="We reply within a day." />
 */
export function Textarea({ label, hint, error, optional, labelAction, className, ...rest }: TextareaProps) {
  const wrapped = Boolean(label || hint || error);
  return withField({ label, hint, error, optional, labelAction, id: rest.id, required: rest.required, disabled: rest.disabled, className }, <TextareaBox {...rest} className={wrapped ? undefined : className} />);
}

Häufige Fragen

Wie installiere ich Textbereich?

Mit aiw add @ai-website/textarea. Der Befehl legt die Datei components/ui/textarea.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind input. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Textbereich?

2 Varianten, Zustände und Eingabebox.

Kann ich die Texte von Textbereich übersetzen?

Ja. Textbereich bringt 2 eigene Texte auf Deutsch und Englisch mit, etwa „Zeichen übrig“ und „Zeichen zu viel“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.

Kann ein KI-Assistent Textbereich einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Textbereich mit aiw_components und installieren sie mit aiw_add @ai-website/textarea.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert