Components

Textarea

A multi-line text input that grows with its content

Installation

leptos-aria add textarea
Install by hand
  1. Copy the source below into src/components/ui/textarea.rs.
  2. Add pub mod textarea; to src/components/ui/mod.rs.
  3. Run cargo add leptos_aria.
  4. Install what it builds on: utils.

Usage

A multi-line text input that grows with its content.

Source

src/components/ui/textarea.rs

//! A multi-line text input that grows with its content.
//!
//! ```rust ignore
//! use crate::components::ui::textarea::Textarea;
//!
//! view! {
//!     <Textarea
//!         label="Message"
//!         placeholder="Type your message here."
//!         on_change=Box::new(|value: &str| leptos::logging::log!("{value}"))
//!     />
//! }
//! ```

use leptos::prelude::*;
use leptos_aria::BoxedTextChangeCallback;
use leptos_aria::TextArea;

use super::utils::cn;

const BASE: &str = "flex field-sizing-content min-h-16 w-full rounded-md border border-input \
                    bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] \
                    outline-none placeholder:text-muted-foreground focus-visible:border-ring \
                    focus-visible:ring-3 focus-visible:ring-ring/50 \
                    disabled:cursor-not-allowed disabled:opacity-50 \
                    aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm \
                    dark:bg-input/30 dark:aria-invalid:ring-destructive/40";

/// A text area. Value state, labelling, and the native `textarea` come from
/// `leptos_aria::TextArea`, which renders its `label` before the field and
/// spreads `class` and `data-slot` onto the field alone.
#[component]
pub fn Textarea(
	/// A visible label, linked to the field with `for`.
	#[prop(optional, into)]
	label: Option<String>,
	/// The value at first (uncontrolled).
	#[prop(optional, into)]
	default_value: Option<String>,
	/// The value (controlled).
	#[prop(optional, into)]
	value: Option<Signal<String>>,
	/// Called with the new value on every keystroke.
	#[prop(optional)]
	on_change: Option<BoxedTextChangeCallback>,
	/// Text shown while the field is empty.
	#[prop(optional, into)]
	placeholder: Option<String>,
	/// The field name used when submitting a form.
	#[prop(optional, into)]
	name: Option<String>,
	/// Whether the field is disabled.
	#[prop(optional, into)]
	is_disabled: Signal<bool>,
	/// Whether the field is read only.
	#[prop(optional, into)]
	is_read_only: Signal<bool>,
	/// Whether the field is required.
	#[prop(optional, into)]
	is_required: Signal<bool>,
	/// The `id` of an element describing the field.
	#[prop(optional, into)]
	description_id: Option<String>,
	/// Classes merged over the defaults; yours win conflicts.
	#[prop(optional, into)]
	class: Signal<String>,
) -> impl IntoView {
	let class = Signal::derive(move || class.with(|class| cn(&[BASE, class])));

	view! {
		<TextArea
			nostrip:label=label
			nostrip:default_value=default_value
			nostrip:value=value
			nostrip:on_change=on_change
			nostrip:placeholder=placeholder
			nostrip:name=name
			is_disabled=is_disabled
			is_read_only=is_read_only
			is_required=is_required
			nostrip:description_id=description_id
			class=class
			attr:data-slot="textarea"
		/>
	}
}