A small caption rendered beneath form fields for helper text or validation messages. 8 variants across Status (Default / Success / Error / Disabled) × Size (Small / Default), each with a leading status icon and a #message plus trailing #label. (Assessed in the 2026 Working File.)
leading-icon whose glyph changes per status, rather than Primary having none and Success/Error hardcoding theirs. A Disabled status was added, the misnamed leadingLabel boolean is gone, and the text surface is two named properties (#message + trailing #label). Schema is a clean Status × Size matrix. It has also earned its place as a standalone primitive — Toggle with Label, Segmented Control - Group, Callout, and Upload File all compose it. Only Code Connect registration remains.Appears directly beneath form fields — Input, Labeled, Select, Recipient, Dropdown — to communicate helper hints, success confirmation, or validation errors.
leading-icon + content structure, with the glyph varying by status. The misnamed leadingLabel boolean is gone; text is now two named properties (#message and a trailing #label). Clean Status × Size matrix, 8 variants, no gaps.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default (helper) | Yes | Yes | Status=Default | Neutral #6780A9 text with a neutral leading icon. Standard helper copy. |
| Success | Yes | Yes | Status=Success | Success palette with a check glyph in the leading icon. |
| Error | Yes | Yes | Status=Error | Error #D61B2C text and icon, with the error glyph — not a recoloured checkmark. |
| Disabled | Yes | Yes | Status=Disabled | Muted text + icon, mirroring a disabled parent field. Added in the rebuild. |
| Size | Yes | Yes | Size=Small / Default | Small for dense rows (15px line), Default for standard form rows (18px). Composes with every Status. |
| Message + label | Yes | Yes | #message · #label | #message carries the helper/validation copy; #label is the trailing text on the right of the row. |
- Code Connect mappings not registered. Anatomy and schema are settled, so registration is unblocked — but the SwiftUI / Compose mappings are not yet wired and the native component does not exist. Snippets remain a Planned API. C7 · Code Connect Linkability
- v2.0: Anatomy made uniform — every variant now carries the same
leading-icon+contentstructure with the glyph varying by status, replacing the old split where Primary had no icon and Success / Error hardcoded theirs. (C4) - v2.0: Misnamed
leadingLabelboolean removed — the text surface is now two named properties,#messageplus a trailing#label, so the name no longer contradicts the rendered position. (C2) - v2.0:
Status=Disabledadded at both sizes, so a disabled parent field has a matched subtext state instead of consumers hiding it or hand-tuning opacity. (C5) - v2.0: Layer naming improved — the icon wrapper is now semantically
leading-icon, and the text nodes are exposed as#message/#labelproperties rather than generic frames. (C1) - v2.0: Status icon glyph confirmed to change per status (check for Success, error mark for Error) rather than a single recoloured checkmark — reviewed and verified. (C6)
- v2.0: Standalone-primitive question settled — it earns its place as a shared component rather than folding into a field slot, since Toggle with Label, Segmented Control - Group, Callout, and Upload File all compose it. (C4)
- v2.0: The leading icon's
shape_fullBOOLEAN_OPERATION is not tracked here — it is owned by the iconography team and fixed at the icon-library level, the same as the Peso Sign. Not a Subtext Message defect. (C6) - v2.1:
Size=BaserenamedSize=Defaultacross all 8 variants — the size step now reads naturally againstSmalland matches howDefaultis used as the standard step elsewhere in the DS. (C2)
- Register Code Connect mapping to
EBSubtextMessage. WireStatusandSize1:1, and map#message/#labelto the native supporting-text parameters. Docs
- Give every variant a uniform leading-icon slot. v2.0: Applied — all four statuses share the same anatomy, with the glyph varying per status. Slot
- Fix the misnamed
leadingLabelboolean. v2.0: Applied — the boolean is gone; text is now#message+ trailing#label. Rename - Add a Disabled variant. v2.0: Applied —
Status=Disabledat both sizes, mirroring the parent field. State - Use semantic layer names. v2.0: Applied —
leading-iconplus#message/#labeltext properties. Rename - Rename
Size=BasetoDefault. v2.1: Applied —Small/Defaultacross all 8 variants. Rename
Neutral helper text. No icon. Used for hints, formatting examples, or ambient guidance under a field.
Each variant binds its own label (and icon, where applicable) token. No appearance modes. No disabled state.
| Role | Token | TOKEN | VALUE |
|---|---|---|---|
| Primary | label | main/subtext-message/primary/label | #6780A9 |
| Success | label | main/subtext-message/success/label | #048570 |
| Success | icon | main/subtext-message/success/icon | #12AF80 |
| Error | label | main/subtext-message/error/label | #D61B2C |
| Error | icon | main/subtext-message/error/icon | #D61B2C |
| Disabled | — | — (missing) | — |
Valid input confirmation. Green text with filled circular checkmark.
Both sizes use the Secondary (BarkAda Semibold) type scale.
| Role | Token | FONT | SIZE | LINE HEIGHT | WEIGHT |
|---|---|---|---|---|---|
| Base | Secondary/Bold/Caption | BarkAda | 12 px | 18 px | Semibold (600) |
| Small | Secondary/Bold/Small Caption | BarkAda | 10 px | 15 px | Semibold (600) |
Validation error. Red text with filled circular close icon.
| Role | Token | TOKEN |
|---|---|---|
| Padding left | 2 px | space/space-2 |
| Padding top | 4 px | space/space-4 |
| Gap (icon ↔ content) | 4 px | space/space-4 |
| Icon frame | 16 × 16 px | — |
| Icon glyph | 12 × 12 px | — |
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.
Assumes the recommended architecture: supportingText slot on each form field (preferred), with this standalone component as a secondary annotation helper.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| Variant = Primary | .ebSubtextStyle(.primary) | style = EBSubtextStyle.Primary |
| Variant = Success | .ebSubtextStyle(.success) | style = EBSubtextStyle.Success |
| Variant = Error | .ebSubtextStyle(.error) | style = EBSubtextStyle.Error |
| Size = Base / Small | .controlSize(.regular / .small) | size = EBSubtextSize.Base / Small |
| leadingLabel (Yes/No) | trailingLabel: String? | trailingLabel: String? = null |
| trailingIcon (Yes/No) | leadingIcon: Image? | leadingIcon: @Composable (() -> Unit)? |
EBInputField("Email", text: $email) .ebError(!isValid) .ebSupportingText("Enter a valid email address")
EBInputField( value = email, onValueChange = { email = it }, placeholder = "Email", isError = !isValid, supportingText = { Text("Enter a valid email address") } )
EBSubtextMessage("Valid message content") .ebSubtextStyle(.success) .controlSize(.small)
EBSubtextMessage( text = "Valid message content", style = EBSubtextStyle.Success, size = EBSubtextSize.Small )
| Requirement | iOS | Android |
|---|---|---|
| Error announcement | Wire to field .accessibilityValue so VoiceOver reads the error with the field value. | Use semantics { error(msg) } on the field, not a standalone live region. |
| Icon is decorative | Mark the leading icon .accessibilityHidden(true) — the text carries the meaning. | Icon contentDescription = null; semantics go on the text. |
| Dynamic Type / font scaling | Caption type must scale with Dynamic Type. Don't hard-lock font size. | Use sp units and respect fontScale. |
| Color-only meaning | Pair red with the close icon so red isn't the sole error cue. | Same — both a color and an icon are required for error/success. |
Do
Pass the message via the parent field's supportingText slot. This keeps validation state and message colocated.
Don't
Render this beneath a field as a separate sibling component — the parent field can't coordinate disabled / error state with an external peer.
Do
Keep error messages specific and actionable ("Enter 11 digits, starting with 09"). Use Primary for ambient hints, Success for confirmation.
Don't
Use Success as a decorative "looks good!" under every valid field — reserve it for meaningful post-validation confirmation.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Requires Rework | Generic container, content, shape_full layers. No semantic slot names. |
| C2 | Variant & Property Naming | Requires Rework | leadingLabel renders trailing — name contradicts position. Booleans already true/false (good). |
| C3 | Token Coverage | Ready | Dedicated main/subtext-message/* tokens for label + icon. Spacing uses space/*. |
| C4 | Native Mappability | Requires Rework | Anatomy diverges by variant. Natively this is a field slot (supportingText), not a peer component. |
| C5 | Interaction State Coverage | Requires Rework | No Disabled variant. Sibling fields have 4 states; subtext has 3 variants. |
| C6 | Asset & Icon Quality | Requires Rework | Icons are shape_full layers — likely flattened / boolean shapes, not vector Icon instances. |
| C7 | Code Connect Linkability | Not Mapped | Blocked until family decision + C1 / C2 / C4 / C6 resolved. |
| Aspect | Status | Notes |
|---|---|---|
| Property naming | Requires Rework | leadingLabel must be renamed to match rendered position |
| Slot inference | Requires Rework | Generic layer names block slot detection |
| State coverage | Requires Rework | Missing Disabled variant |
| Native component file | Not Mapped | Depends on family decision: standalone EBSubtextMessage or field supportingText slot |
3 Variant values × 2 Size values. The leadingLabel and trailingIcon booleans are not part of the 6 — they toggle at the instance level.
| Variant | Size | Node ID |
|---|---|---|
| Primary | Base | 11855:8764 |
| Primary | Small | 11855:8767 |
| Success | Base | 11855:8770 |
| Success | Small | 11855:8776 |
| Error | Base | 11855:8782 |
| Error | Small | 11855:8788 |
shape_full — Inner 12×12 glyph carries a generic, flattened-style name. Suggests raster fill or boolean op rather than a proper vector Icon instance.
Opencontainer / content don't describe role.
OpensupportingText slot vs keep standalone) + C1 / C2 / C4 / C6.
Open