A message-composer input with a text area, leading attachment, and trailing send action.
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 }.Canonical contexts: chat threads, customer-support conversations, peer-to-peer messaging, and comment composers docked to the bottom of a scroll view.
active. Error, Disabled, and isFilled states that Input Field ships are silently unreachable through Chat Field's surface API.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.| 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. |
- Icon frames are generically named
container. Both the leading (plus/attach) and trailing (send) slots share the layer namecontainer, so Code Connect can't tell leading from trailing. Should be semantic:leading/trailing(orattach-slot/send-slot) to match nativeHStackordering. C1 · Layer Structure & Naming - Boolean uses
yes/noand misnames the intent.active=yes/nocannot map to SwiftBool/ KotlinBooleanwithout a translation layer, andactiveis ambiguous — the true meaning is "inner field is focused". The property is redundant with the inner Input Field'sState=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 privateactiveboolean 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) andSend 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
- 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 withmultiline/lineLimit1…5 per the Text Area consolidation),trailingAction(Icon Button instance). Drop theactiveboolean — 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/noaway. 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
leadingActionandtrailingActionas 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_FullandSend Message Mediumwith vector icon components tinted viamain/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% opacityor a muted token when the field has no text. Currently not representable. Covered naturally by exposingtrailingActionas 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
Message composer with a leading attachment icon, inner input field, and trailing send action. Flip Active to focus the field.
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 |
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.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| (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 |
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) } )
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() )
| Requirement | iOS | Android |
|---|---|---|
| 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) |
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.
| 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. |
| 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 |
Single axis: active (no/yes). Both variants are 360×88px.
| active | Size | Inner field border | Node ID |
|---|---|---|---|
| no | 360×88 | 1px #D7E0EF | 23:145916 |
| yes | 360×88 | 2px #005CE5 | 23:145922 |
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.
DocumentedEBChatComposer 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.
Openactive=yes/no cannot map to Swift Bool / Kotlin Boolean, and the property duplicates the inner field's State=Active.
OpenAdd_Full (plus) and Send Message Medium (paper-plane) ship as PNG references instead of vectors, on a 32×32 frame.
Opencontainer — Leading and trailing icon wrappers both use the same generic layer name; Code Connect cannot distinguish them.
Open