Skip to content
Components

Composer

Bring an input, attachments and actions together into a compact writing surface.

TS
import { … } from "@fungi.computer/caps/blocks/composer";

Basic

TSX
import { Composer } from "@fungi.computer/caps/blocks/composer";
import { SendAction } from "@fungi.computer/caps/components/send-action";
import { Textarea } from "@fungi.computer/caps/components/textarea";
export default function Basic() {
return (
<Composer
className="w-full max-w-sm"
input={
<Textarea
aria-label="Message"
placeholder="Ask about today's harvest"
rows={1}
/>
}
action={<SendAction aria-label="Send" state="ready" />}
/>
);
}

Toolbar

TSX
import { Composer } from "@fungi.computer/caps/blocks/composer";
import { KeycapButton } from "@fungi.computer/caps/components/keycap";
import { SendAction } from "@fungi.computer/caps/components/send-action";
import { Textarea } from "@fungi.computer/caps/components/textarea";
export default function Toolbar() {
return (
<Composer
className="w-full max-w-sm"
input={
<Textarea aria-label="Message" placeholder="Reply to Spora" rows={1} />
}
toolbar={
<>
<KeycapButton
aria-label="Attach"
icon={<span aria-hidden="true">+</span>}
label={null}
className="w-auto"
/>
<span className="text-xs">spora/field-guide</span>
</>
}
action={<SendAction aria-label="Send" state="ready" />}
/>
);
}

Interactive

TSX
import { Composer } from "@fungi.computer/caps/blocks/composer";
import { SendAction } from "@fungi.computer/caps/components/send-action";
import { Textarea } from "@fungi.computer/caps/components/textarea";
import { useState } from "react";
export default function Interactive() {
const [draft, setDraft] = useState("");
const [sent, setSent] = useState<string[]>([]);
const send = () => {
if (!draft.trim()) return;
setSent((previous) => [...previous, draft.trim()]);
setDraft("");
};
return (
<div className="flex flex-col gap-sm">
<Composer
className="w-full max-w-sm"
input={
<Textarea
aria-label="Message"
placeholder="Note a new find"
rows={1}
value={draft}
onChange={(event) => {
setDraft(event.target.value);
}}
/>
}
action={
<SendAction
aria-label="Send"
onClick={send}
state={draft.trim() ? "ready" : "empty"}
/>
}
/>
<ul className="text-sm">
{sent.map((note, index) => (
<li key={index}>{note}</li>
))}
</ul>
</div>
);
}

Props

ComposerProps

PropTypeDescription
inputReactNodeThe caller-owned field, typed into directly: the surface is the field.
action?ReactNodeThe caller-owned key at the end of the bottom row, such as a SendAction.
toolbar?ReactNodeCaller-owned controls and readouts along the bottom edge, before the action.
attachments?ReactNodeCaller-owned items riding with the draft, shown above the text.
  • inputReactNode

    The caller-owned field, typed into directly: the surface is the field.

  • action?ReactNode

    The caller-owned key at the end of the bottom row, such as a SendAction.

  • toolbar?ReactNode

    Caller-owned controls and readouts along the bottom edge, before the action.

  • attachments?ReactNode

    Caller-owned items riding with the draft, shown above the text.

Full types: API reference.