KeepReady
Text Area Component link

A multi-line text input for longer-form entry — label, body, and character counter.

Keep — all findings resolved
Rebuilt on node 4781:35856 in the 2026 Working File. State = Default | Focused | Error | Disabled × hasValue = False | True gives eight variants, the label and subtext are shared instances rather than redrawn, focus and error borders are distinct, the character counter matches its sample exactly, and the local layers now read ValueContainerValue. Two family questions were settled here rather than left to recur: hasValue is the pattern for fields whose filled state changes geometry, and the error color on a validation message belongs to the shared Subtext Message component. All four DS Health traits pass; the only item still open is Code Connect, blocked until the native library exists.
In Context

Typical mobile contexts: feedback forms, message composers, notes, support request descriptions.

Live Preview
Placeholder
Properties
state
isFilled
isExpandable
DS Health
Reusable
Pass
Works anywhere multi-line text is needed, carries its own label and supporting-text anatomy, and is confirmed as a standalone component. The State × hasValue schema mirrors Input Field's by design — family consistency rather than duplication.
Self-contained
Pass
Carries its own border, fill, and text styles per state, plus a FormGroup Header for labelling and a Subtext Message for supporting copy and the character count. Nothing external required to render a complete field.
Consistent
Pass
Axes are clean — State = Default | Focused | Error | Disabled × hasValue, PascalCase per §1 with Title Case values — and hasValue is now the documented family pattern for fields whose filled state changes geometry. Local layers follow the §3 vocabulary as ValueContainerValue, the label and subtext come from shared instances, and the character counter matches its sample content.
Composable
Pass
Composes shared FormGroup Header and Subtext Message instances, so label and supporting-text changes propagate from one source across the family rather than being re-authored per component.
Behavior
State iOS Android Figma Property Notes
Default Yes Yes State=Default 1px #D7E0EF border, white bg. No resize glyph — removed in v2.2.
Focused Yes Yes State=Focused #005CE5 border. Matches SwiftUI @FocusState / Compose FocusRequester vocabulary.
Error Yes Yes State=Error #D61B2C border. Validation copy goes in the Subtext Message row below.
Disabled Yes Yes State=Disabled #EEF2F9 bg, border hidden, text #C2CFE5.
Issues
  • Code Connect mappings not registered. Blocked — no native library exists yet. The property schema is clean and every layer is semantically named, so mapping is a mechanical step once the library lands. C7 · Code Connect Linkability
  • Boolean property migrated to True/False. v2.0: Rebuilt on node 4781:35856 in the 2026 Working File. isFilled is now a real Figma boolean with True/False values, mapping directly to Swift Bool / Kotlin Boolean. Matches the fix Input Field shipped in 1.1.0. (C2 · Rename) C2 · Variant & Property Naming
  • State=Active renamed to Focused. v2.0: The interaction axis is now State = Default | Focused | Error | Disabled, matching SwiftUI @FocusState / Compose FocusRequester vocabulary and the Focused value in §5 of the Property Naming Guidelines. Consistent with Search Field. (C2 · Rename) C2 · Variant & Property Naming
  • Label and supporting-text slots added. v2.0: Each variant now composes a FormGroup Header instance above the field and a Subtext Message instance below it, so labelling and validation copy are part of the component rather than re-implemented per screen. Delivered as shared instances rather than bespoke slots — a better outcome than recommended. (C5 · Slot) C5 · Interaction State Coverage
  • Character-count affordance added. v2.0: The Subtext Message instance carries a trailing counter (0/100) alongside the supporting-text row, matching Material 3's supportingText + counter pattern. The DS can now represent limit state. (C5 · Slot) C5 · Interaction State Coverage
  • Confirmed as a standalone component. v2.1: Closed by owner decision — Text Area stays its own component rather than folding into Input Field as a multiline flag. The original consolidation case rested on Text Area duplicating Input Field's schema with no anatomy of its own; it now composes shared FormGroup Header and Subtext Message instances and owns a character-count affordance Input Field doesn't need. The parallel State × isFilled schema is deliberate family consistency, not duplication. (Family) C4 · Native Mappability
  • State=Error exception confirmed. v2.1: Closed by owner decision at family level — Error stays on the State axis across Form Elements as a deliberate exception to the State/Status rule, matching how most design systems model form-field validation. Keeps Text Area at 8 variants rather than the 12 a split would require. Applies equally to Search Field and Amount Text Field. (C2 · Property) C2 · Variant & Property Naming
  • Desktop resize handle removed. v2.2: The text-area icon frame is gone from all eight variants, taking the browser-only resize: both affordance with it. Native TextField(axis: .vertical) / OutlinedTextField(maxLines = n) auto-grow without a user-facing grip, so the component now maps cleanly. Field heights normalised to 50px and every variant is a uniform 94px tall. Also closes the raster-vs-vector question, which is moot now the glyph is gone. (C4 · C6 · Asset) C4 · Native Mappability
  • Token namespace resolved — generic tokens applied. v2.2: Closed by owner confirmation. The component-scoped main/text-area/* namespace was dropped in favour of the shared generic token scale, so there is no longer a parallel token set that can drift from main/input-field/*. Same direction Search Field took. (C1 · Token) C1 · Layer Structure & Naming
  • Character counter corrected in the filled variants. v2.2: The four hasValue=True variants now read 71/100, matching their body copy, instead of the previous 0/100. (C1) C1 · Layer Structure & Naming
  • Boolean renamed isFilledhasValue. v2.3: The variant axis is now State × hasValue. Resolves the catalog gap in the right direction — §2 assigns the has prefix to presence of content, and hasValue pairs with the Value text property in §3, where isFilled read as a visual descriptor. Input Field and Amount Text Field should follow so the family stays aligned. (C2 · Rename) C2 · Variant & Property Naming
  • Nested duplicate text-container removed. v2.3: text-container now holds #text-label directly in every variant — the redundant same-size wrapper inside it is gone, and the layer tree is unambiguous. (C1) C1 · Layer Structure & Naming
  • Sample content matches hasValue in all eight variants. v2.4: Every hasValue=False variant now reads Write your message… with a 0/100 counter, and every hasValue=True variant carries the body sentence with 71/100 — verified across all eight by text scan. Field heights follow the content: 34px for one line of placeholder, 50px for two lines of copy. Closes the last C1 finding. (C1) C1 · Layer Structure & Naming
  • Character-count sample content corrected. v2.4: Verified on the live node. The counter now reads 0/100 in the hasValue=False variants and 71/100 in the hasValue=True variants, matching the 71-character sample string exactly. Previously the count and the content disagreed, which made the counter look decorative rather than bound. (C1) C1 · Layer Structure & Naming
  • Local layer names cleaned up. v2.5: Verified on the live node. text-containerValueContainer and #text-labelValue across all eight variants, so the entered text exposes as a single Value property matching the §3 vocabulary. The bordered frame keeps the name Text Area; it is the only frame at that level and Figma already nests it under the component, so the shadowing is accepted rather than churned. The remaining #label and #subtext layers belong to the shared FormGroup Header and Subtext Message instances and are their owner’s to rename. (C1 · Rename) C1 · Layer Structure & Naming
  • hasValue settled as a family rule. v2.5: Both approaches in Form Elements are correct, and the rule that separates them is layout. Where the filled state changes the component’s geometry it needs an explicit boolean, because the two shapes cannot be one variant: Text Area is 78px empty and 94px filled, so hasValue earns its place and the set is eight. Where filled changes only color and affordance — Search Field, whose height is constant at 56px — the state is derived from value presence in code and the set stays at four. A consumer can now tell from the geometry alone which pattern a field follows. hasValue goes into the §2 approved boolean catalog on that basis. (C2 · Family) C2 · Variant & Property Naming
  • Error-state subtext coloring settled. v2.5: The family rule is that a validation message carries the error color — Amount Text Field turns its HelperText #D61B2C and is the reference. Text Area’s subtext is not a local layer: it is the shared Subtext Message instance, which also carries the character counter, so the error color belongs to that component rather than to this one. Recorded here as a shared-component follow-up rather than a Text Area defect, so the difference reads as a known boundary instead of an inconsistency. (C5 · Token) C5 · Interaction State Coverage
Styles
Default
DES DEV

Idle state with gray border. Resize-handle glyph sits in the bottom-right regardless of fill.

Placeholder
Properties
isFilled
isExpandable
Properties
state Default
isFilled no
isExpandable true
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Min height 120px
Padding H 12px
Padding V 14px
Border radius radius/radius-2 (6px)
Border 1px solid
Resize vertical, snap to line height
Typography
Value style Primary/Label/Light/Small
Value font Proxima Soft Semibold · 14 / 14 · +0.25
Colors by State

All four color roles are bound to main/text-area/color/{state}/* tokens. Every value mirrors the equivalent main/input-field/color/* token — motivation for consolidation.

Role Token DEFAULTACTIVEERRORDISABLED
Border text-area/color/{state}/border #D7E0EF #005CE5 #D61B2C hidden
Background text-area/color/{state}/bg #FFFFFF #FFFFFF #FFFFFF #EEF2F9
Text (filled) text-area/color/{state}/text #0A2757 #0A2757 #0A2757 #C2CFE5
Placeholder text-area/color/{state}/placeholder #C2CFE5 #C2CFE5 #C2CFE5 #C2CFE5
Resize glyph text-area/color/{state}/icon-resizer #D7E0EF #D7E0EF #D7E0EF #D7E0EF
Active (Focused)
DES DEV

Focused state with 2px blue border. Rename target: <code>focused</code>.

Placeholder
Properties
isFilled
isExpandable
Properties
state Active
isFilled no
isExpandable true
Colors
Bg #FFFFFF
Border #005CE5
Text #0A2757
Placeholder #90A8D0
Layout
Min height 120px
Padding H 12px
Padding V 14px
Border radius radius/radius-2 (6px)
Border 1px solid
Resize vertical, snap to line height
Typography
Value style Primary/Label/Light/Small
Value font Proxima Soft Semibold · 14 / 14 · +0.25
Error
DES DEV

Validation error state with 2px red border. No inline error-message slot — copy is the consumer's responsibility.

Placeholder
Properties
isFilled
isExpandable
Properties
state Error
isFilled no
isExpandable true
Colors
Bg #FFFFFF
Border #D61B2C
Text #0A2757
Placeholder #90A8D0
Layout
Min height 120px
Padding H 12px
Padding V 14px
Border radius radius/radius-2 (6px)
Border 1px solid
Resize vertical, snap to line height
Typography
Value style Primary/Label/Light/Small
Value font Proxima Soft Semibold · 14 / 14 · +0.25
Disabled
DES DEV

Non-interactive state with gray fill and muted text. Border hidden.

Placeholder
Properties
isFilled
isExpandable
Properties
state Disabled
isFilled no
isExpandable true
Colors
Bg #EEF2F9
Border #D7E0EF
Text #90A8D0
Placeholder #C2CFE5
Layout
Min height 120px
Padding H 12px
Padding V 14px
Border radius radius/radius-2 (6px)
Border 1px solid
Resize vertical, snap to line height
Typography
Value style Primary/Label/Light/Small
Value font Proxima Soft Semibold · 14 / 14 · +0.25
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:form-elements:1.0.0")
}

Import

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

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

Property Mapping
Figma PropertySwiftUICompose
(text content) text: Binding<String> value: String
isFilled (yes/no)
(multi-line default) axis: .vertical singleLine = false
(auto-grow range) .lineLimit(3...6) maxLines = 6
state = default
state = active @FocusState interactionSource
state = error .ebError(true) isError = true
state = disabled .disabled(true) enabled = false
isExpandable
SwiftUI
ios/Components/FormElements/EBInputField.swift
Jetpack Compose
android/components/form/EBInputField.kt
Usage Snippets Planned API
Multi-line (default)
EBInputField("Tell us more", text: $value, axis: .vertical)
    .lineLimit(3...6)
EBInputField(
    value = text,
    onValueChange = { text = it },
    placeholder = "Tell us more",
    singleLine = false,
    maxLines = 6
)
Error
EBInputField("Tell us more", text: $value, axis: .vertical)
    .lineLimit(3...6)
    .ebError(true)
EBInputField(
    value = text,
    onValueChange = { text = it },
    placeholder = "Tell us more",
    singleLine = false,
    maxLines = 6,
    isError = true
)
Disabled
EBInputField("Tell us more", text: $value, axis: .vertical)
    .lineLimit(3...6)
    .disabled(true)
EBInputField(
    value = text,
    onValueChange = { text = it },
    placeholder = "Tell us more",
    singleLine = false,
    maxLines = 6,
    enabled = false
)
Accessibility
RequirementiOSAndroid
Minimum touch target 44 x 44 pt (per-line height 22pt, container ≥44pt) 48 x 48 dp
Accessibility label .accessibilityLabel("Comment") contentDescription
Error announcement VoiceOver reads error via .accessibilityValue TalkBack reads error via semantics { error() }
Character-count announcement Announce remaining via .accessibilityValue when a limit is set Expose via supportingText semantics
Usage Guidelines

Do

Use for free-form responses expected to exceed one line — feedback, comments, messages, notes.

Don't

Use for short structured inputs (name, phone, code) — Input Field's single-line default is more appropriate and faster to fill.

Do

Pair with a visible label above the field and a helper-text row below for character counts or format hints.

Don't

Rely on the desktop resize handle on mobile — mobile fields auto-grow within lineLimit/maxLines and the handle has no native behavior.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Needs Refinement Duplicate token namespace main/text-area/* mirrors main/input-field/* exactly. Text layer structure is clean.
C2 Variant & Property Naming Requires Rework isFilled=yes/no (same anti-pattern Input Field already resolved). state=active should be focused.
C3 Token Coverage Ready All colors bound to main/text-area/color/*. Spacing and radius tokens resolved.
C4 Native Mappability Requires Rework Exists as a distinct component but native platforms treat multi-line as a single TextField with axis: .vertical / singleLine=false. Desktop resize handle has no native equivalent.
C5 Interaction State Coverage Needs Refinement All 4 interaction states present. Missing slots: label, helper/error text, character count.
C6 Asset & Icon Quality Requires Rework Resize glyph is a raster PNG referenced four times (once per state) instead of a single vector instance.
C7 Code Connect Linkability Not Mapped Blocked by the consolidation decision and property renames.
Code Connect
Aspect Status Notes
Property naming Requires Rework isFilled=yes/no cannot map to native booleans
Component identity Requires Rework Native platforms have no standalone TextArea primitive; consolidation into Input Field is required first
Native component file Needs Refinement Proposed target: EBInputField with multi-line flag
Variants Inventory (8 total)

4 state values × 2 isFilled values.

stateisFilledHeightNode ID
defaultyes62px3070:21242
defaultno46px3070:21239
activeyes62px3070:21243
activeno46px3070:21238
erroryes62px3070:21244
errorno46px3070:21240
disabledyes62px3070:21241
disabledno46px3070:21237
1.0.0 — April 2026Major
Initial Assessment · node 3070:21245
Component assessed — 8 variants documented across state (default/active/error/disabled) × isFilled (yes/no). Multi-line sibling of Input Field within the Form Elements group. Documented
Initial
Consolidation proposed — Fold Text Area into Input Field via a multiline / lineLimit prop to match SwiftUI axis: .vertical and Compose singleLine=false. Open
Family
Boolean property uses Yes/NoisFilled=yes/no instead of true/false. Same anti-pattern Input Field already resolved. Open
C2 Open
Desktop resize-handle glyph — 12×12px raster PNG in the bottom-right has no native mobile equivalent; iOS/Android auto-grow without a user-facing handle. Open
C4, C6 Open
Duplicate token namespacemain/text-area/color/* values mirror main/input-field/color/* exactly; candidate for aliasing or deletion after consolidation. Open
C1 Open
Code Connect mappings — No CLI mappings registered yet. Blocked by consolidation decision and property renames. Open
C7 Open