Skip to content

FRD: Compose Panel

Document Summary

FieldDetails
Feature NameCompose Panel
StatusDraft
OwnerDavid Holmes
ContributorsDesign, Engineering
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #53
Last Updated2026-05-26

Introduction

Overview

Compose Panel is a compact message-composition widget that composes TextArea, Button, and Badge into a form with To field, subject line, body, and send action. It provides a ready-to-use compose surface for SaaS messaging, email-like interfaces, and notification senders. The widget is intentionally minimal (size S) and does not include rich-text editing, attachments, or templates.

Goals

  • Deliver a <ComposePanel> component with To, Subject, and Body fields plus a Send button.
  • Compose existing TextArea, Button, and Badge primitives.
  • Validate required fields (To, Body) before enabling send.
  • Ship Storybook stories for the compose flow and validation states.

Non-Goals

  • Rich-text or WYSIWYG editing.
  • File or image attachments.
  • Address book / contact autocomplete.
  • Draft auto-save or persistence.
  • CC/BCC fields (deferred to future enhancement).

Scope

In Scope

AreaDescription
Component<ComposePanel> with To, Subject, Body fields and Send button
To FieldText input for recipient; supports comma-separated entries displayed as badges
Subject FieldSingle-line text input
Body FieldMulti-line TextArea for message body
ValidationSend button disabled until To and Body are non-empty
Send CallbackonSend fires with structured payload
StatesEmpty (initial), validation errors, sending (loading), send error
StoriesStorybook stories for compose flow, validation, and states

Out of Scope

AreaReason
Rich-text editingAdds complexity beyond S-size scope
File attachmentsSeparate feature
Contact autocompleteApplication-layer concern
CC/BCCFuture enhancement
Draft persistenceApplication-layer concern

Users and Pain Points

User Groups

UserDescriptionNeeds
DevelopersEngineers building SaaS messaging or notification featuresA composable compose form using DS primitives
DesignersDesign-system consumersConsistent compose UX aligned with DS tokens
End UsersSaaS application usersSimple, clear compose interface for sending messages

Pain Points

UserPain PointImpact
DevelopersBuilding compose forms from scratch with recipient badges, validation, and send stateRepeated boilerplate across projects
End UsersInconsistent compose UIs across SaaS appsLearning curve, error-prone messaging

Definitions

TermDefinition
Compose PanelA form widget for composing and sending a message
Recipient BadgeA styled tag showing a recipient’s address, removable by click
Send PayloadThe structured data object passed to onSend: { to, subject, body }

Current State

Existing Behavior

The design system has TextArea, Button, and Badge as individual components. There is no compose-form widget that assembles them into a messaging composition surface.

Current Limitations

  • No compose-panel pattern exists.
  • Recipient badge rendering (comma-separated text to badge list) must be implemented from scratch.
  • Validation logic for compose forms is left to each consumer.

Existing Workarounds

  • Developers build compose forms ad hoc with raw input, textarea, and button elements.

Proposed Solution

Summary

Introduce <ComposePanel> that renders a compact form with a To field (text input that converts comma-separated entries into removable Badge components), a Subject text input, a Body TextArea, and a Send Button. The Send button is disabled until required fields are filled. On submit, onSend fires with a structured payload.

Key Capabilities

  • To field with comma/Enter-triggered badge creation and click-to-remove.
  • Subject field (optional but visible).
  • Body TextArea with auto-grow.
  • Send button with loading state during send.
  • Field validation with inline error messages.
  • Cancel/discard action.

User Experience

Users see a compact form. They type recipients in the To field; pressing comma or Enter converts the text to a badge. They fill in an optional subject and a message body, then click Send. If To or Body is empty, the Send button is disabled and inline errors appear on blur.

Developer Experience

<ComposePanel
onSend={({ to, subject, body }) => sendMessage({ to, subject, body })}
onCancel={() => closePanel()}
/>

Requirements

IDRequirementPriorityNotes
FR-001ComposePanel renders To, Subject, and Body fields with Send buttonMust-
FR-002To field converts text to removable Badge componentsMust-
FR-003Send button is disabled until To and Body are non-emptyMust-
FR-004onSend fires with structured { to, subject, body } payloadMust-
FR-005Send button shows loading state during async sendShould-
FR-006Cancel/discard action is availableShould-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001To field input: typing text and pressing comma or Enter creates a Badge for that entryQuick recipient entryMust
FUNC-002Clicking the remove icon on a recipient Badge removes itEasy correctionMust
FUNC-003Subject field is a single-line text inputStandard compose patternMust
FUNC-004Body field is a TextArea with auto-grow up to a max heightComfortable compositionMust
FUNC-005Send button disabled when to.length === 0 or body.trim() === ""Prevents incomplete sendsMust
FUNC-006onSend receives { to: string[]; subject: string; body: string }Typed, structured payloadMust
FUNC-007isSending prop or internal state disables form and shows button loading indicatorPrevents double-sendShould
FUNC-008onCancel fires when cancel/discard button is clickedPanel dismissalShould
FUNC-009Inline validation errors show on To and Body fields on blur when emptyClear error feedbackMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Form renders in under 20msPerformanceMust
NFR-002All fields and buttons are keyboard-navigable in logical tab orderAccessibilityMust
NFR-003Works in light and dark themesThemingMust
NFR-004No new runtime dependenciesMaintainabilityMust

API / Interface Requirements

Public API

NameTypeDescriptionRequired
onSend(payload: ComposePayload) => void | Promise&lt;void&gt;Send callback with structured dataYes
onCancel() => voidCancel/discard callbackNo
defaultTostring[]Pre-filled recipientsNo
defaultSubjectstringPre-filled subjectNo
defaultBodystringPre-filled bodyNo
isSendingbooleanExternal sending stateNo
classNamestringAdditional CSS classesNo

ComposePayload Type

interface ComposePayload {
to: string[];
subject: string;
body: string;
}

Example Usage

import { ComposePanel } from "@/components/ui/compose-panel";
<ComposePanel
onSend={async ({ to, subject, body }) => {
await api.sendMessage({ to, subject, body });
}}
onCancel={() => setShowCompose(false)}
defaultTo={["alice@example.com"]}
/>

API Notes

  • onSend can return a Promise; the component auto-manages isSending state if a Promise is returned.
  • If isSending is provided as a prop, it overrides the auto-managed state.
  • Recipient badges display the text as-is; email validation is the consumer’s responsibility.

Accessibility Requirements

IDRequirementNotes
A11Y-001To field has aria-label “Recipients” and badge list has role="list"-
A11Y-002Each recipient badge is focusable with a remove button that has aria-labele.g., “Remove alice@example.com
A11Y-003Subject and Body fields have associated &lt;label&gt; elementsStandard form accessibility
A11Y-004Send button has descriptive text; disabled state is conveyed to screen readersaria-disabled when conditions not met
A11Y-005Inline validation errors are linked to fields via aria-describedby-

Checklist

  • Keyboard support is defined.
  • Focus behavior is defined.
  • Screen reader behavior is defined.
  • Color contrast requirements are met.
  • Reduced motion behavior is considered.
  • Semantic HTML expectations are documented.
  • ARIA usage is defined only where needed.

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page with overview and props tableStorybookMust
DOC-002”When to use / When not to use” guidanceStorybook docsMust
DOC-003Stories for empty compose, pre-filled, validation errors, and sending stateStorybookMust

Dependencies

DependencyTypeOwnerStatusNotes
TextAreaEngineeringDesign SystemReadyBody field
ButtonEngineeringDesign SystemReadySend and cancel buttons
BadgeEngineeringDesign SystemReadyRecipient badges
Design tokensDesignDesign SystemReady-

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
No email validation built inInvalid recipients may be sentDocument that validation is consumer responsibility; consider adding a validateRecipient callback
No CC/BCCLimited for full email use casesS-size scope; CC/BCC can be added later without breaking changes
No rich textLimited formatting for longer messagesIntentionally minimal; rich-text compose is a separate, larger widget

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should the To field support a validateRecipient callback for inline validation?David HolmesOpen
Q-002Should the panel support a “Save as Draft” action?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001To field converts comma/Enter input to Badge componentsFR-002, FUNC-001
AC-002Recipient badges are removable by clicking the remove iconFUNC-002
AC-003Send button is disabled when To or Body is emptyFR-003, FUNC-005
AC-004onSend fires with { to, subject, body } payload on sendFR-004, FUNC-006
AC-005Loading state disables form during sendFUNC-007
AC-006Inline validation errors appear on blur for empty required fieldsFUNC-009
AC-007All Storybook stories render without errorsDOC-003
AC-008Component passes axe accessibility auditNFR-002

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • Use TextArea for the body field, Button for send/cancel, Badge for recipient tags.
  • Implement comma/Enter-to-badge conversion in the To field.
  • Disable Send when required fields are empty.
  • Auto-manage isSending from Promise return of onSend.
  • Place stories under the SaaS Widgets Storybook section.

LLM Should Not

  • Add rich-text editing or markdown support.
  • Add file attachment support.
  • Build contact autocomplete.
  • Add CC/BCC fields.
  • Modify existing TextArea, Button, or Badge components.

Decision Log

DateDecisionReasonOwner
2026-05-26Keep scope minimal (S-size): To, Subject, Body, Send onlyFastest path to a useful compose widget; can be extended incrementallyDavid Holmes
2026-05-26No built-in email validationValidation rules vary by product; consumer responsibilityDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft