RestructureRequires Rework
Chat Field Component link

A message-composer input with a text area, leading attachment, and trailing send action.

Restructure as a composed pattern
Chat Field wraps an Input Field instance with two icon buttons and exposes only a single active boolean. That schema drops Error, Disabled, and isFilled coverage that the inner Input Field already carries, and the leading/trailing glyphs ship as rasters. Rename to Chat Composer (or Message Composer), expose the field as a nested instance with its full state matrix, and replace both 32×32 icon slots with Icon Button instances so native can map 1:1 to HStack { Button + TextField + Button }.
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
Partial
Works anywhere a message composer is needed, but the 360px fixed width and single layout (leading-attach + field + send) assume one use case. No variants for audio-only composers, multi-attach rows, or send-disabled states.
Self-contained
Warn
Bundles its own layout and icons but delegates all field styling and state to a nested Input Field, while exposing only active. Error, Disabled, and isFilled states that Input Field ships are silently unreachable through Chat Field's surface API.
Consistent
Warn
active=yes/no (C2 anti-pattern). The property forwards to the inner field's State=Active, so a chat-field-level boolean duplicates a field-level enum. The two leading/trailing icon frames are named container rather than semantic slot names like leading / trailing.
Composable
Partial
Already nests an Input Field instance — good. But the leading and trailing icons are fixed raster glyphs, not Icon Button instances, so the composer cannot be recomposed for an emoji picker, voice-note, or camera entry point without editing the component.
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.
Open Issues
  • Icon frames are generically named container. Both the leading (plus/attach) and trailing (send) slots share the layer name container, so Code Connect can't tell leading from trailing. Should be semantic: leading / trailing (or attach-slot / send-slot) to match native HStack ordering. C1 · Layer Structure & Naming
  • Boolean uses yes/no and misnames the intent. active=yes/no cannot map to Swift Bool / Kotlin Boolean without a translation layer, and active is ambiguous — the true meaning is "inner field is focused". The property is redundant with the inner Input Field's State=Active. C2 · Variant & Property Naming
  • Composer maps to three native siblings, not a single primitive. iOS and Android both compose this pattern as HStack { Button + TextField + Button } / Row { IconButton + OutlinedTextField + IconButton }. Shipping Chat Field as a single Figma component with a private active boolean hides that composition from Code Connect, making 1:1 mapping impossible. C4 · Native Mappability
  • 2 variants cover less state than the inner Input Field already has. Input Field ships 8 variants (State × isFilled). Chat Field wraps it but collapses the surface to a single Active toggle — Error, Disabled, and the Default-but-filled case are unreachable through Chat Field. C5 · Interaction State Coverage
  • Leading and trailing icons are raster PNGs. Both Add_Full (plus, 32×32) and Send Message Medium (paper-plane, 32×32) are referenced via PNG asset URLs (imgShapeFull / imgShapeFull1). Native platforms ship vector SF Symbols (plus, paperplane.fill) / Material icons (Add, Send); the rasters cannot tint, scale, or accept token-based color. C6 · Asset & Icon Quality
  • Code Connect mappings not registered. Blocked by the composition decision, property rename, and icon vectorization. Once the composer is rebuilt as Input Field + two Icon Button slots, mapping is a direct pass-through to the sibling components' own Code Connect entries. C7 · Code Connect Linkability
Design Recommendations
  • Rename to Chat Composer (or Message Composer) and rebuild as a composed pattern. Target schema: a 3-slot layout — leadingAction (Icon Button instance), field (Input Field instance with multiline/lineLimit 1…5 per the Text Area consolidation), trailingAction (Icon Button instance). Drop the active boolean — focus comes from the nested field. Result: 0 net variants on the composer, full field state matrix inherited, and both icons become recomposable. Family
  • Rename active=yes/no away. If the composer keeps any boolean at all, use semantic naming tied to a real affordance — e.g. sendEnabled: true/false — and let the field's own focus state carry the focused appearance. Rename
  • Expose leadingAction and trailingAction as Figma Slots. Consumers will need to swap plus for camera, microphone, or emoji entry points, and paper-plane for voice-note-record without forking the component. Slots also let the layer names convey leading vs trailing, fixing C1. Slot
  • Vectorize both action glyphs. Replace the PNG Add_Full and Send Message Medium with vector icon components tinted via main/chat-field/color/icon. Aligns with the token that already exists and with SF Symbols / Material icons on native. Asset
  • Add a send-disabled visual for empty fields. Common chat pattern: the send icon dims to ~40% opacity or a muted token when the field has no text. Currently not representable. Covered naturally by exposing trailingAction as an Icon Button slot with its own enabled/disabled variant. State
  • Document a Chat component family. This is the first component in the Chat group. Plan siblings now: Chat Bubble (sent/received), Chat Bubble (with attachment), Chat Typing Indicator, Chat Date Separator. Defining the family shape now prevents each bubble from being drawn ad-hoc in product files. Family
Types
Default
DES DEV

Message composer with a leading attachment icon, inner input field, and trailing send action. Flip Active to focus the field.

Properties
Active
Properties
Active no
Colors
Surface #FFFFFF
Border #D7E0EF
Input text #90A8D0
Send icon #005CE5
Layout
Field height 48 (auto-grow)
Padding H 12
Padding V 14
Border radius 6
Border width 1
Send button 40 × 40
Typography
Style Primary/Multi-line Label/Light/Base
Font Proxima Soft Semibold · 16 / 20 · +0.25
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

// In Xcode: File → Add Package Dependencies
"https://github.com/AY-Org/eb-ds-ios"

Android — Gradle (Kotlin DSL)

dependencies {
    implementation("com.eastblue.ds:chat:1.0.0")
    implementation("com.eastblue.ds:form-elements:1.0.0")
}

Import

import EastBlueDS  // SwiftUI
import com.eastblue.ds.chat.*  // Compose
import com.eastblue.ds.form.*

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.
C7 Code Connect Linkability Not Mapped Blocked by composition rebuild, property rename, and icon vectorization.
Code Connect
Aspect Status Notes
Property naming Requires Rework active=yes/no cannot map to native booleans and duplicates the nested field's focus
Component identity Requires Rework Native platforms compose this pattern from three siblings — Chat Field needs to ship as a composition, not a primitive
Icon slots Requires Rework Leading/trailing icon frames are fixed rasters, not Icon Button instances
Native component file Needs Refinement Proposed target: EBChatComposer under a new Chat package
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.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/Noactive=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