Browse all of Kata

Belt dot and rank picker

Source docs/design/components/belt-dot.mdMarkdown

On this page

BeltDot draws a rank as a small ringed circle. RankPicker lists a dojo's own ladder as rows to choose from; RankPickerSheet puts that list in a sheet.

Light

ranked · 10

roster · 16

subject · 28

white · ring on

black · ring on

shodan

godan

hachidan

kudan

Dark

ranked · 10

roster · 16

subject · 28

white · ring on

black · ring on

shodan

godan

hachidan

kudan

Source: BeltDot.tsx, RankPicker.tsx. The law behind both: rank. The swatches above use the launch community's ladder as example data; a screen renders whatever ladder the dojo has.

When to use it

  • BeltDot beside a rank's name, or beside a person whose rank is also written nearby.
  • RankPicker inline in a form, RankPickerSheet from a select trigger.

Never hardcode a ladder, a rank name or a rank colour into a screen. BeltRankPicker does, which is why it is retired: it can only draw nineteen Cuong Nhu ranks.

Anatomy

BeltDot

Part Token
Size swatchSize.ranked 10 (has a rank; legal only where the rank is also text), swatchSize.roster 16 (the rank matters: rosters, pickers), swatchSize.subject 28 (the rank is the subject)
Fill the ladder's own visual: solid, stripes, split, end-cap or alternating bands — the dojo's data, not Konjo's palette
Ring 1px border.control, every rank, both themes, no exceptions

RankPicker

Light

Selected row carries a check · unavailable rows state why

White
One green stripe
Two green stripes
Green
BrownNeeds 6 more months

Dark

Selected row carries a check · unavailable rows state why

White
One green stripe
Two green stripes
Green
BrownNeeds 6 more months
Part Token
Row controlHeight.row 64 floor, spacing.s12 padding and gap
Swatch BeltDot at swatchSize.roster 16
Name typography.rowTitle, text.primary
Selected a check at 16 in text.primary on the right
Unavailable full contrast, with its reason in caption text.secondary on the right
Divider 1px border.hairline on surface.card, inset to the text edge

States

Light

Loading · skeleton rows

Error · bordered retry

Couldn’t load the ranks

The connection dropped. Your choices are kept.

Try again

Dark

Loading · skeleton rows

Error · bordered retry

Couldn’t load the ranks

The connection dropped. Your choices are kept.

Try again
  • Selected: a check, never a filled row.
  • Unavailable: not dimmed. There is no legal disabled text colour, so the row stays readable and says why ("Needs 6 more months").
  • Loading: four skeleton rows of the same shape, never a centred spinner.
  • Error: "Couldn't load the ranks", plain words, and a bordered "Try again" — the screen owns the primary.
  • Empty: "No ranks set up", and where an owner fixes it.
  • Pressed: opacity.pressed 0.85 on the row.

Content

  • The rank's name is always beside the swatch. Two ranks can draw identically — nidan and rokudan are both two red stripes on black.
  • Shared components say "rank", not "kyu" or "dan": those are one lineage's words.

Accessibility

  • BeltDot is hidden from screen readers. The rank reaches them through the row's label; announcing it twice is worse than not at all.
  • The ring is not decoration. Belt fills get no contrast guarantee, and against a card a white belt, a yellow belt and, in dark, a black belt are close to invisible. border.control on surface.card is 3.53:1 in light and 3.16:1 in dark, whatever colour it surrounds.
  • Each picker row is one stop, role button, with accessibilityState={{ selected, disabled }} and a label that includes the reason when it is unavailable.

Do and don't

Nidan

Do

The swatch sits beside the rank’s name.

Don’t

Swatches alone. Nidan and rokudan draw the same.

Code

import { BeltDot, RankPicker } from '../../components/ui';
import { swatchSize } from '../../theme/tokens';

<BeltDot visual={rank.visual} size={swatchSize.roster} />

<RankPicker
  ranks={ladder}
  selectedSlug={chosen?.slug}
  onSelect={setChosen}
  unavailable={{ brown: 'Needs 6 more months' }}
  loading={ladderLoading}
  error={ladderError}
  onRetry={reloadLadder}
  testID="promotion-rank-picker"
/>