A small caption rendered beneath form fields for helper text or validation messages.
leadingLabel boolean, generic shape_full icon layers, no disabled state. Decide: keep as standalone primitive or fold into form-field supportingText slot.Appears directly beneath form fields — Input, Labeled, Select, Recipient, Dropdown — to communicate helper hints, success confirmation, or validation errors.
shape_full — not instance-swapped from the DS Icon library.leadingLabel is misnamed (the Label text renders trailing in the flex row).supportingText) treat this as a field slot, not a peer component — suggesting it should be folded in.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Primary (helper) | Yes | Yes | Variant=Primary | Neutral weaker text #6780A9. No icon. |
| Success | Yes | Yes | Variant=Success | Green text #048570, filled check icon #12AF80. |
| Error | Yes | Yes | Variant=Error | Red text + icon #D61B2C. |
| Disabled | N/A | N/A | — | No disabled variant today. When parent field is disabled, there's no matched subtext state. |
- Anatomy diverges by variant. Primary has no icon, Success / Error hardcode specific icons (Checkmark Circular / Close). The "leading icon" is not a uniform slot — it's an if-branch on Variant. Consumers can't override the Success / Error icon without detaching. C4 · Native Mappability
-
leadingLabelboolean is misnamed. The "Label" text actually renders on the trailing side of the flex row (after the message content), not leading. Naming contradicts rendered position and will mislead SwiftUI / Compose param names downstream. C2 · Variant & Property Naming - Icon layer named
shape_full. The inner 12×12 shape inside the Checkmark / Close containers carries a generic, flattened-style name. Suggests a raster image fill or BOOLEAN_OPERATION rather than a proper vector Icon instance swappable from the DS Icon library. C6 · Asset & Icon Quality - Container layers named generically. Nested frames labeled
containerandcontentdon't describe role — Code Connect slot inference relies on semantic names like#leading-icon,#message,#label. C1 · Layer Structure & Naming - No Disabled variant. When the parent field is disabled, the subtext has no paired state — consumers either hide it or rely on manual opacity adjustments. Every sibling form field carries a Disabled state; the subtext should too. C5 · Interaction State Coverage
- Code Connect mappings not registered. Blocked behind C1 / C2 / C4 / C6. Also depends on the family-level decision below — whether this stays a standalone component or folds into the form-field
supportingTextslot. C7 · Code Connect Linkability
- Fold Subtext Message into each form field as a
supportingTextslot. This is the native convention on both platforms — Material 3TextFieldexposessupportingText, and SwiftUI pairs aTextunder the field using the same validation state. Folding it in removes the C4 / C5 gaps (field already has Disabled + Error states) and lets consumers drive the message by passingsupportingText: String?+ deriving color fromisError. The standalone component can stay as an annotation helper for designers but is no longer the canonical consumer integration path. Family - Rename
leadingLabel→trailingLabel. The "Label" text renders at the end of the flex row — the property name must match rendered position so SwiftUI / Compose params don't surprise developers. If the intent is to actually make the Label leading, swap the layer order in Figma and keep the current name. Rename - Normalize anatomy with a real leading-icon slot across all variants. Rebuild so every variant shares the same structure: optional
#leading-icon(Icon instance) →#message(text) → optional#trailing-label(Label text). Primary keeps icon = off by default; Success / Error default icon = on. This lets oneVariantenum + one boolean icon slot + one boolean label slot cover all six variants uniformly. Property - Replace
shape_fullwith DS Icon library instances. Swap the Success checkmark and Error close forIcon=Checkmark CircularandIcon=Closeinstance-swaps bound tomain/subtext-message/*/icontokens. Flattened boolean shapes can't be retinted, recolored, or resized cleanly and they block Code Connect 1:1 icon param mapping. Asset - Add a Disabled variant. Match the 4-state contract of every sibling form field (Default / Active / Error / Disabled). When the parent field is disabled, the subtext needs a paired muted color token (e.g.
main/subtext-message/disabled/label). State - Rename container layers.
container→#leading-icon-slot,content→#content, inner shape →#icon-glyph. Semantic names drive Code Connect slot inference. 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