East Blue
KeepNeeds Refinement
Chat Composer Component link

A message composer that arranges a leading action slot, a composed Input Field and a trailing send slot into one row.

Keep — all findings resolved
Rebuilt on node 5536:31209 in the 2026 Working File, and the restructure has landed in full: renamed Chat Composer, rebuilt as a composed pattern around a real Input Field instance, with leadingAction and trailingAction as genuine Figma Slots holding their icon instances directly, both action glyphs vectorised from the shared icon library, and a send control that dims when there is nothing to send. The schema is now isActive × hasValue — two independent booleans carrying the §2 prefix with Title Case values, naming what actually differs between variants rather than the effect that follows. Disabled and error states are scoped out for this release by owner decision. All four DS Health traits pass; the only item still open is Code Connect, blocked until the native library exists.
In Context

Canonical contexts: chat threads, customer-support conversations, peer-to-peer messaging, and comment composers docked to the bottom of a scroll view.

Live Preview
Say hi!
Properties
active
DS Health
Reusable
Pass
A generic composer row — the two action slots make it work for chat, comments or any send-a-message surface, rather than binding it to one screen.
Self-contained
Pass
Owns its row layout and spacing; the field, the icons and the slot contents all come from published DS components, so nothing is redrawn locally.
Consistent
Pass
The schema is isActive × hasValue — both §2 booleans in lowerCamelCase with Title Case values per §5 — naming the field’s focus and content rather than the effects that follow from them. Structure matches: real Figma Slots holding their instances directly on both sides, library icon instances, and a composed Input Field. Note for handoff: what this component calls isActive is what its Form Elements siblings express as State=Focused.
Composable
Pass
Two real Figma Slots around a composed Input Field instance. It arranges DS parts rather than reimplementing them, and a consumer can swap either action without detaching.
Behavior
State iOS Android Figma Property Notes
Default (inactive) Yes Yes active=no Inner field shows 1px #D7E0EF border, placeholder text "Say hi!".
Active (focused) Yes Yes active=yes Inner field shows 2px #005CE5 border. Text color switches from placeholder to filled.
Filled (has content) N/A N/A (not represented) No distinct variant. Inner field's isFilled is pinned by the active toggle; the composer can't model "typed but unfocused".
Error N/A N/A (not represented) Input Field supports Error; Chat Field does not expose it.
Disabled / send-disabled N/A N/A (not represented) No disabled state for the composer, and no way to dim the send icon when the field is empty.
Issues
  • Renamed to Chat Composer and rebuilt as a composed pattern. v2.0: Rebuilt on node 5536:31209 in the 2026 Working File. The component no longer redraws a text field — it composes a real Input Field instance between two action slots, which is exactly the restructure the previous assessment asked for. It is now a layout that arranges existing DS parts rather than a primitive competing with one, and a fix to Input Field reaches it for free. (C4 · Composition) C4 · Native Mappability
  • leadingAction and trailingAction are real Figma Slots. v2.0: Both are genuine SLOT nodes rather than frames standing in for them, so a consumer swaps in their own attach control or send affordance without detaching. This also gives the native handoff two named content slots to bind rather than fixed children. (C1 · Slot) C1 · Layer Structure & Naming
  • Action glyphs are vectors, not rasters. v2.0: Both PNGs are gone. The leading control is an Add_Full instance carrying a shape_full boolean operation, and the trailing control is a Send Message Medium instance — both from the shared icon library, so they recolor from tokens and stay crisp at any density. Confirmed visually as well as structurally. (C6 · Asset) C6 · Asset & Icon Quality
  • Send-disabled visual added. v2.0: The trailing send control now dims when there is nothing to send — a pale blue paper plane at sendEnabled=false against the full #005CE5 at sendEnabled=true, alongside the field showing placeholder copy rather than a value. This is the recommendation applied: an empty composer no longer offers a send affordance that looks live. Verified by export rather than from the layer tree. (C5 · State) C5 · Interaction State Coverage
  • active=yes/no retired; variant count doubled. v2.0: The old two-variant set with a yes/no boolean is replaced by two axes over four variants — a state axis carrying default and active (the field border moving #D7E0EF → #005CE5), and a send-enablement boolean using real true/false values. The set now depicts focus and content independently rather than collapsing them. Property casing still needs a pass and is tracked below. (C2 · Property) C2 · Variant & Property Naming
  • Trailing action slot dropped its generic wrapper. v2.0: trailingAction holds its icon instance directly, with no intermediate frame named container. The leading slot still carries one, tracked below. (C1) C1 · Layer Structure & Naming
  • Property schema rebuilt as two booleans. v2.1: Verified on the live node. state = default | active and sendEnabled are replaced by isActive = False | True × hasValue = False | True. Both carry the §2 is / has prefix in lowerCamelCase with Title Case values per §5, and expressing focus as a boolean rather than a two-value enum is the better fit — there were only ever two positions, and the two axes are genuinely independent: a composer can be focused and empty, or unfocused and full. (C2 · Rename) C2 · Variant & Property Naming
  • Axis renamed to name the cause, not the effect. v2.1: sendEnabled → hasValue. The axis now describes what actually differs between the variants — the field holds content — with the send control’s appearance following from it. That removes the impossible combination the old name allowed, where a designer could set sendEnabled=true on an empty composer, and aligns the vocabulary with Text Area. (C2 · Rename) C2 · Variant & Property Naming
  • Action slots evened up. v2.1: The container frame is gone from leadingAction, which now holds its Add_Full instance directly, matching trailingAction. Both slots are structurally identical, so a consumer swapping either one meets the same shape. (C1) C1 · Layer Structure & Naming
  • hasValue as an explicit axis — recorded exception. v2.1: The family rule set on Text Area is that hasValue earns a variant axis where the filled state changes geometry, and is derived in code where it changes only color — which is why Search Field has no such boolean. Chat Composer is 88px in all four variants, so it looks like it should derive too. It keeps the axis deliberately: unlike a bare field, the composer owns a separate interactive control whose enabled state has to be specified for handoff, and the send button’s two appearances are a spec a developer needs to see rather than infer. Recorded here so the apparent contradiction reads as a considered exception. (C2 · Docs) C2 · Variant & Property Naming
  • Disabled and error states scoped out for now. v2.1: Confirmed by the owner — the composer ships focus and content only. Unavailable conversations are out of scope for this release rather than unhandled, so a native implementation should not expect a dimmed composer to bind to. When offline, muted, rate-limited or blocked-recipient cases are taken on, they arrive together as one state pass rather than being added piecemeal. Recorded so the absence reads as scope rather than omission. (C5) C5 · Interaction State Coverage
Types
Default
Properties
Active
Properties
Active no
Colors
Surface #FFFFFF
Border #D7E0EF
Input text #90A8D0
Send icon #005CE5
Typography
Style Primary/Multi-line Label/Light/Base
Layout
Field height 48 (auto-grow)
Padding H 12
Padding V 14
Radius 6
Border 1
Send button 40 × 40
Colors by State

The composer owns only two tokens (bg, icon). Every other color is inherited from the nested Input Field's main/input-field/* collection — another motivation for rebuilding the composer as a composition rather than a primitive.

Role Token DEFAULT (active=no)ACTIVE (active=yes)
Composer bg main/chat-field/color/bg #FFFFFF #FFFFFF
Leading icon (plus) main/chat-field/color/icon #005CE5 #005CE5
Trailing icon (send) main/chat-field/color/icon #005CE5 #005CE5
Field bg main/input-field/{state}/bg #FFFFFF #FFFFFF
Field border main/input-field/{state}/border #D7E0EF (1px) #005CE5 (2px)
Field placeholder main/input-field/default/placeholder #90A8D0 #90A8D0
Field text (filled) main/input-field/default/text #0A2757 #0A2757
Installation Planned API

iOS — Swift Package Manager

.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")

Android — Gradle (Kotlin DSL)

implementation("com.eastblue.ds:chat:1.0.0")

Import

import EastBlueDS
import com.eastblue.ds.chat.*

Package not yet published. These are the planned distribution paths.

Property Mapping
Figma PropertySwiftUICompose
(text content) text: Binding<String> value: String
active (yes/no) @FocusState interactionSource
(leading slot) leadingAction: () -> Void leadingAction: () -> Unit
(trailing slot) trailingAction: () -> Void trailingAction: () -> Unit
(line growth) axis: .vertical, lineLimit(1...5) singleLine = false, maxLines = 5
(placeholder) placeholder: String placeholder: String
SwiftUI
ios/Components/Chat/EBChatComposer.swift
Jetpack Compose
android/components/chat/EBChatComposer.kt
Usage Snippets Planned API
Inactive composer
EBChatComposer(
    placeholder: "Say hi!",
    text: $message,
    onAttach: { presentAttachSheet() },
    onSend:   { send(message) }
)
EBChatComposer(
    value = message,
    onValueChange = { message = it },
    placeholder = "Say hi!",
    onAttach = { presentAttachSheet() },
    onSend = { send(message) }
)
Active with send-disabled
EBChatComposer(
    placeholder: "Say hi!",
    text: $message,
    onAttach: attach,
    onSend:   send,
    sendEnabled: !message.isEmpty
)
EBChatComposer(
    value = message,
    onValueChange = { message = it },
    placeholder = "Say hi!",
    onAttach = attach,
    onSend = send,
    sendEnabled = message.isNotEmpty()
)
Accessibility
RequirementiOSAndroid
Minimum touch target (icon buttons) 44 × 44 pt — 32px frame pads to 44pt hit area 48 × 48 dp — 32px frame pads to 48dp hit area
Leading icon label .accessibilityLabel("Attach") contentDescription = "Attach"
Trailing icon label .accessibilityLabel("Send") contentDescription = "Send"
Send-disabled announcement VoiceOver reads "Send, dimmed" via .accessibilityHint("Enter a message first") TalkBack reads disabled state via semantics { disabled() }
Keyboard submit .onSubmit { send() } on the nested TextField keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send)
Usage Guidelines

Do

Dock the composer to the bottom of the chat scroll view with the keyboard inset, and let the field auto-grow within lineLimit(1...5) / maxLines = 5.

Don't

Use Chat Field for single-line structured inputs (name, phone, amount). Use Input Field or the typed form-element sibling instead.

Do

Dim the trailing send icon when the field is empty. Reflects system chat conventions (iMessage, WhatsApp, Messenger) and prevents empty-send.

Don't

Swap the plus icon for unrelated actions (navigation, close). Leading slot is reserved for content-entry affordances: attach, camera, mic, emoji.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Needs Refinement Both icon frames share the generic name container. Should be leading / trailing.
C2 Variant & Property Naming Requires Rework active=yes/no — boolean naming and semantic intent both wrong; the property duplicates the inner field's State=Active.
C3 Token Coverage Ready main/chat-field/color/bg and main/chat-field/color/icon resolved. Spacing (space/space-8, 12, 16, 24) and radius (radius/radius-2) bound.
C4 Native Mappability Requires Rework No native primitive for "chat composer"; maps to HStack { Button + TextField + Button }. Shipping as a single component hides the composition.
C5 Interaction State Coverage Requires Rework Only Active/Inactive. Missing Error, Disabled, isFilled, and send-disabled states that the use case requires.
C6 Asset & Icon Quality Requires Rework Both leading (plus) and trailing (send) glyphs are raster PNGs; must vectorize and bind to main/chat-field/color/icon.
Variants Inventory (2 total)

Single axis: active (no/yes). Both variants are 360×88px.

activeSizeInner field borderNode ID
no360×881px #D7E0EF23:145916
yes360×882px #005CE523:145922
1.0.1 — September 2026Patch
Documentation backfill · node 5536:31209
Resolved issues recorded. 11 resolved issues on the Overview tab had no changelog row: Renamed to Chat Composer and rebuilt as a composed pattern; leadingAction and trailingAction are real Figma Slots; Action glyphs are vectors, not rasters; Send-disabled visual added; active=yes/no retired; variant count doubled; Trailing action slot dropped its generic wrapper; Property schema rebuilt as two booleans; Axis renamed to name the cause, not the effect; Action slots evened up; hasValue as an explicit axis — recorded exception; Disabled and error states scoped out for now.
Docs
1.0.0 — April 2026Major
Initial Assessment · node 23:145915
Component assessed — 2 variants documented on a single active axis. First component in the new Chat group. Anatomy: leading plus icon (32px raster) + nested Input Field + trailing send icon (32px raster) in a 360×88 container. Documented
Initial
Restructure proposed — Rebuild as a composition (EBChatComposer wrapping EBInputField + two EBIconButton slots) and rename to Chat Composer / Message Composer. Drops the ambiguous active boolean and inherits Input Field's full state matrix. Open
Family
Boolean property uses Yes/No — active=yes/no cannot map to Swift Bool / Kotlin Boolean, and the property duplicates the inner field's State=Active. Open
C2 Open
Raster leading/trailing glyphs — Both Add_Full (plus) and Send Message Medium (paper-plane) ship as PNG references instead of vectors, on a 32×32 frame. Open
C6 Open
Hidden state gaps — Composer surface exposes only Default/Active. Error, Disabled, isFilled, and send-disabled cannot be represented at this layer despite being native requirements for the chat use case. Open
C5 Open
Icon frames share the name container — Leading and trailing icon wrappers both use the same generic layer name; Code Connect cannot distinguish them. Open
C1 Open
Code Connect mappings — No CLI mappings registered. Blocked by composition rebuild and property renames. Open
C7 Open