The one selectable cell used everywhere in the picker — day numbers in the calendar grid, and month and year labels in the header views. 16 versions.
Date Picker - Item and Month and Year Picker - Item are gone; both are now Kind values on this one component. The old Type setting that mixed display role with selection state is split into Role and Selection, every version wraps in a Container, and the Today ring is identical across both kinds. It is badged Needs Refinement for one reason: the cell is 32×32, below the 44×44 and 48×48 minimum touch targets.Day cells fill the seven-column calendar grid; month and year cells fill the three-column header views. The same component serves both, which is what keeps selection, today and disabled looking identical wherever they appear.
Kind value now tells them apart.Container holding one #text. Selection is #005CE5 with white text in both; Today is a #005CE5 ring in both; Selected text is SemiBold 600 in both.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Kind=Day | N/A | N/A | 32 × 32 | Circular container, radius 24. |
| Kind=MonthYear | N/A | N/A | 59 × 32 | Rounded rectangle, radius 8. Header views only. |
| Role=Today | N/A | N/A | ring | #005CE5 stroke and text, no fill. |
| Role=Prev-Next | N/A | N/A | muted | Adjacent-month days. Tappable, not disabled. |
| Selection=Selected | N/A | N/A | filled | #005CE5 fill, white text. Also carries the pressed meaning. |
| Selection=Range-* | N/A | N/A | strip | #E5F1FF strip bleeding 10px past the cell to meet its neighbour. Day only. |
| isDisabled=true | N/A | N/A | muted | Not available on the range or Prev-Next versions. |
- The 32 × 32 cell is deliberate. A seven-column grid at 312px leaves 32px per cell, so the drawn circle stays 32 × 32 by design. The tap target is not the circle: it expands natively to 44 pt on iOS and 48 dp on Android, which the Accessibility table sets out for both platforms. Confirmed with design — the intended trade for now, not a gap. Sizing the cell to its target is logged as a recommendation for a future redesign. C5 · Interaction State Coverage
- Two cell components became one.
Date Picker - ItemandMonth and Year Picker - Itemare gone. Both are nowKindvalues on this component, which is what stopped the two from drifting apart on state coverage, ring thickness and selection colour. C2 · Variant & Property Naming - Display role and selection are separate settings. The old
Typesetting mixed the two, so "this is today" and "this is selected" competed for one slot. They are nowRoleandSelection, which is what made the Today and Selected combination expressible at all. C2 · Variant & Property Naming - Value names lost their punctuation and spacing.
Range (Middle)is nowRange-Middle, alongsideRange-StartandRange-End.KindreadsMonthYearrather than carrying a slash, and the booleans are lowercasetrueandfalse. C2 · Variant & Property Naming - Every version wraps the same way. Day cells used to wrap in
containerand month cells inLabel Container, and the month cell's inner frame was calledMontheven on the year view. All sixteen now readContainer→#text. C1 · Layer Structure & Naming - The range strip is drawn once, consistently. Range-Start carried two overlapping rectangles whose names sat on the wrong sides, Range-Middle painted
#E5F1FFtwice, and the plain Selected cell shipped two hidden range rectangles it never used. Each range version now has one rectangle, correctly named, bleeding the same 10px. C1 · Layer Structure & Naming - Selected reads the same weight on both kinds. Month and year labels were Bold 700 while day numbers were SemiBold 600 in the same
Selection=Selectedstate. Both are SemiBold 600 now. C1 · Layer Structure & Naming - Today and Selected no longer pretend to differ. Four
Role=Today, Selection=Selectedversions existed but rendered identically toRole=Default, Selection=Selected— the day cell's only difference was a stroke in the same colour as its own fill. They were dropped rather than given a treatment: once a date is selected, the selected fill is the signal. C5 · Interaction State Coverage - Adjacent-month days got a stronger colour. Prev-Next moved from the weakest text token to the next step up, taking contrast against white from roughly 2.4:1 to about 4.0:1. These are real tappable days, not disabled ones, so they need to be readable. C3 · Token Coverage
- Size the cell to the touch target in a future redesign. The 32 × 32 cell is deliberate today — the seven-column grid leaves 32px, and the tap target is expanded natively to 44 pt / 48 dp. But an expanded target the user cannot see still misleads: two adjacent days sit 32px apart while both claim 44. Worth revisiting whenever the calendar’s grid is next opened up, so the drawn cell and the tappable one are the same thing. Not a blocker for this release. A11y
- Rename the component to
Date Picker - Cellin Figma. The component is still calledPicker Cellat node5943:41825. The family agreed on theDate Picker -prefix so the five components group together in the assets panel. Rename - Document that Selected carries the pressed meaning. There is no separate pressed version, by design — tapping a cell selects it. Worth noting in the Code tab that Compose attaches a ripple to clickable surfaces by default, so Android will show press feedback the design does not draw. Docs
- Give the row the range strip, not the cell. v2.1: Closed — decided against. The strip stays on the cell and bleeds past its edge on purpose: it meets the neighbouring cell so the highlight reads as one continuous bar across the row. Moving it to the row would mean the row deciding which days are in range, which is the cell's job. Composition
- Take Prev-Next one more step up the token scale. v2.1: Applied — Prev-Next sits on
text/color-text-weaker. That is the next step available that does not read as disabled, chosen over a higher-contrast value on purpose: going further would make an adjacent-month day look unavailable. Token
The cell has no pressed state, so that column is blank throughout. Range and Prev-Next exist only on Day and have no disabled version.
| Role | Token | Default | Pressed | Disabled |
|---|---|---|---|---|
| Container surface | bg/color-bg-primary-inverse | #FFFFFF | – | #FFFFFF |
| #text | text/color-text · disabled text/color-text-disabled | #0A2757 | – | #C2CFE5 |
| #text — Today | text/color-text-primary · disabled text/color-text-primary-disabled | #005CE5 | – | #9BC5FD |
| Border — Today | border/color-border-primary · disabled border/color-border-primary-disabled | #005CE5 | – | #9BC5FD |
| Container — Selected | bg/color-bg-primary · disabled bg/color-bg-primary-disabled | #005CE5 | – | #9BC5FD |
| #text — Selected | text/color-text-inverse | #FFFFFF | – | #FFFFFF |
| Range highlight | bg/color-bg-info-weakest | #E5F1FF | – | – |
| #text — Prev-Next | text/color-text-weaker | #6780A9 | – | – |
Identical palette to Day — the two kinds differ in width and corner radius, never in colour. MonthYear has no Range or Prev-Next version.
| Role | Token | Default | Pressed | Disabled |
|---|---|---|---|---|
| Container surface | bg/color-bg-primary-inverse | #FFFFFF | – | #FFFFFF |
| #text | text/color-text · disabled text/color-text-disabled | #0A2757 | – | #C2CFE5 |
| #text — Today | text/color-text-primary · disabled text/color-text-primary-disabled | #005CE5 | – | #9BC5FD |
| Border — Today | border/color-border-primary · disabled border/color-border-primary-disabled | #005CE5 | – | #9BC5FD |
| Container — Selected | bg/color-bg-primary · disabled bg/color-bg-primary-disabled | #005CE5 | – | #9BC5FD |
| #text — Selected | text/color-text-inverse | #FFFFFF | – | #FFFFFF |
iOS — Swift Package Manager
.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")
Android — Gradle (Kotlin DSL)
implementation("com.eastblue.ds:date-picker:1.0.0")
Import
import EastBlueDS import com.eastblue.ds.datepicker.*
Planned API — the native library does not exist yet. The artifact is the Date Picker family, not this component: Cell, Header, Header Trigger, Calendar and the picker itself all ship in <code>com.eastblue.ds:date-picker</code> and import <code>com.eastblue.ds.datepicker.*</code>. In practice most teams never call this directly; it is the cell the calendar grid renders.
Figma properties mapped to the intended native parameters, in the order the Figma property panel lists them. Each cell shows one value — the day number or the month/year label — so the two text properties are alternatives, never both at once.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| Kind — Day, MonthYear | kind: EBDatePickerCellKind | kind = EBDatePickerCellKind.Day / MonthYear |
| Role — Default, Today, Prev-Next | role: EBDatePickerCellRole = .default | role: EBDatePickerCellRole = Default |
| Selection — None, Selected, Range-Start, Range-Middle, Range-End | selection: EBDatePickerCellSelection = .none | selection: EBDatePickerCellSelection = None |
| isDisabled — true, false | isDisabled: Bool = false | isDisabled: Boolean = false |
| Day (text) — shown when Kind is Day | day: Int | day: Int |
| Text (text) — shown when Kind is MonthYear | text: String | text: String |
EBDatePickerCell( kind: .day, day: 1 )
EBDatePickerCell( kind = EBDatePickerCellKind.Day, day = 1 )
EBDatePickerCell( kind: .monthYear, text: "Label" )
EBDatePickerCell( kind = EBDatePickerCellKind.MonthYear, text = "Label" )
| Requirement | iOS | Android |
|---|---|---|
| Tap area meets the platform minimum | .frame(minWidth: 44, minHeight: 44) around the 32pt circle | Modifier.minimumInteractiveComponentSize() |
| The date is announced, not the number | .accessibilityLabel(date.formatted(date: .complete, time: .omitted)) | contentDescription built from the full date |
| Selection is a state, not part of the label | .accessibilityAddTraits(.isSelected) | Modifier.semantics { selected = true } |
| Today is announced | Append "Today" to the label when Role=Today | Same, via stateDescription |
Do
Use Prev-Next for days belonging to the neighbouring month — they stay tappable.
Don't
Don't use Disabled for adjacent-month days; disabled means unavailable.
Do
Let Selected carry the pressed meaning — tapping a cell selects it.
Don't
Don't add a separate pressed style that competes with selection.
Do
Keep range selections on Day cells.
Don't
Don't try to range-select months or years; those views pick one value.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | All sixteen versions read Container → #text. Range rectangles are single, correctly named, and bleed a consistent 10px. |
| C2 | Variant & Property Naming | Ready | Kind, Role, Selection and isDisabled follow the naming guidelines, with lowercase booleans and no punctuation in values. |
| C3 | Token Coverage | Ready | Every colour is bound and confirmed by design. Prev-Next uses text/color-text-weaker — the next step available that does not read as disabled, chosen deliberately over a higher-contrast value. |
| C4 | Native Mappability | Ready | Maps to a cell on both platforms. The range strip bleeds past the cell on purpose: it meets the neighbouring cell so the highlight reads as one continuous bar across the row. |
| C5 | Interaction State Coverage | Ready | States are complete for the intended set — no Focused, since these are mobile components, and Selected carries the pressed meaning. The 32 × 32 cell is a deliberate visual size; the tap target is expanded natively to 44 pt / 48 dp, as the Accessibility table sets out. |
| C6 | Asset & Icon Quality | Ready | No icons or rasters — the cell is type on a shape. |
2 Kind × 3 Role × 5 Selection × 2 isDisabled permits 60 combinations; 16 are built. The matrix is sparse on purpose — MonthYear takes no range and no Prev-Next, range cells have no disabled version, and Prev-Next takes no selection.
| Kind | Versions | Count |
|---|---|---|
| Day | Default · Today · Prev-Next, with the full Selection range | 10 |
| MonthYear | Default · Today, None and Selected only | 6 |
View full Kind × Role × Selection breakdown (16 rows)
| Kind | Role | Selection | isDisabled | Node |
|---|---|---|---|---|
| Day | Default | None | false | 5943:41826 |
| Day | Default | None | true | 5943:41829 |
| Day | Today | None | false | 5943:41832 |
| Day | Today | None | true | 5943:41835 |
| Day | Default | Selected | false | 5943:41838 |
| Day | Default | Selected | true | 5943:41843 |
| Day | Default | Range-Start | false | 6442:72593 |
| Day | Default | Range-Middle | false | 5943:41848 |
| Day | Default | Range-End | false | 6442:72603 |
| Day | Prev-Next | None | false | 5943:41853 |
| MonthYear | Default | None | false | 6442:73668 |
| MonthYear | Default | None | true | 6442:73665 |
| MonthYear | Today | None | false | 6442:73671 |
| MonthYear | Today | None | true | 6442:73674 |
| MonthYear | Default | Selected | false | 6442:73662 |
| MonthYear | Default | Selected | true | 6442:73677 |
com.eastblue.ds:date-picker-cell, which was correct when v2.1.0 shipped and stopped being correct the same week: §3.2 now rules that an artifact is a family, never a component. Cell, Header, Header Trigger, Calendar and the picker itself all install com.eastblue.ds:date-picker. The Kotlin import was already on the family package and did not move..eb-preview-dpcell declared font-family: inherit, which reads as a deliberate choice in a diff but resolves through .eb-preview-scope to body — the documentation font, BarkAda. The cell is Proxima Soft. The font was loaded the whole time; the preview simply never asked for it. Found on Header Trigger and swept back here.kind === 'day' ? '1' : 'Label' and never read the value the control had already stored. It now takes the text and falls back to Figma’s defaults only when the field is empty.index and MonthYear’s label — the property names Figma used before v2.1.0 renamed them to Day and Text. Nothing user-facing was wrong, but the data file and the demo script disagreed with the Code tab. All nine references now read day and text.Day for the date number and Text for the month or year — named after what each shows rather than the generic Index and Label they started as. This is what makes the release a minor rather than a patch.Value property would serve both kinds, but Figma gives a text property one placeholder for every version — and "Value" overflows a 32 × 32 day cell. Two properties is the deliberate trade.Primary/SemiBold/Body, which is not a style in the database. The layer resolves to Primary/Label/Light/Small.text/color-text-weaker deliberately: the next step up would read as disabled.Container paints white on all sixteen versions.#C2CFE5, which is Default's disabled text. Today's disabled state is #9BC5FD on both.Kind=Day and Kind=MonthYear. Titles are the bare value.EBPickerCell while the Code tab and the live snippet called it EBDatePickerCell, and the argument names disagreed too — index against day. All three surfaces now build from one definition.gcash/east-blue-ios and com.gcash.eastblue:components became AY-Org/eb-ds-ios and com.eastblue.ds:date-picker-cell:2.1.0, with the Kotlin import on the family package com.eastblue.ds.datepicker.Day and MonthYear, the two values of the driving setting. Property Mapping gained the two text settings, and the Variants Inventory gained the full sixteen-row breakdown behind its summary.text/color-text-weaker is the last step before an adjacent-month day starts reading as unavailable. C3 moves to Ready.Date Picker - Item and Month and Year Picker - Item fold into this component as Kind=Day and Kind=MonthYear.Type split into Role and Selection. Display role and selection state no longer compete for one setting.Range (Middle) → Range-Middle, Month/Year → MonthYear, booleans lowercased.container and Label Container both became Container.