Transport Badge
A transit station badge for Singapore MRT/LRT and Hong Kong MTR with semantic theme colours by default and optional official line colours.
Preview
<div class="flex flex-col gap-3">
<x-ui.transport-badge system="SG" station-code="NS1" :station-name="__('Jurong East')" :show-station-name="true" />
<x-ui.transport-badge system="SG" station-code="DT15" :station-name="__('Bugis')" :show-station-name="true" />
<x-ui.transport-badge system="HK" station-code="TW3" :station-name="__('Tsim Sha Tsui')" :show-station-name="true" />
</div>
Installation
php artisan ui:add transport-badge
Registry contract
php artisan ui:add transport-badge
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/transport-badge.blade.php -
resources/css/themes/transport-badge.css
- Registry dependencies
- None — installs on its own.
- Packages
-
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Transport Badge (`transport-badge`)
A transit station badge for Singapore MRT/LRT and Hong Kong MTR with semantic theme colours by default and optional official line colours.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add transport-badge
```
## Usage
```blade
<div class="flex flex-col gap-3">
<x-ui.transport-badge system="SG" station-code="NS1" :station-name="__('Jurong East')" :show-station-name="true" />
<x-ui.transport-badge system="SG" station-code="DT15" :station-name="__('Bugis')" :show-station-name="true" />
<x-ui.transport-badge system="HK" station-code="TW3" :station-name="__('Tsim Sha Tsui')" :show-station-name="true" />
</div>
```
## Props
- `system` (string, default `SG`) — Transit system whose line colors and label apply, such as SG (Singapore) or HK (Hong Kong).
- `stationCode` (mixed|null, default `null`) — One line code, or an array of codes for an interchange station.
- `stationName` (mixed|null, default `null`) — Station name used in the accessible label and shown when showStationName is true.
- `showStationName` (bool, default `false`) — Shows the station name as visible text next to the code chips.
- `size` (md|xs|sm|lg, default `md`) — Chip size: xs, sm, md, or lg.
- `colorMode` (string, default `semantic`) — semantic uses theme tokens for every chip; brand uses each line's official transit color.
## Use when
- Use for compact labels, counts, and concise metadata that support scanning.
- Showing a transit line or station code with its official identity, such as in a directions list or station card.
- Displaying several line codes for an interchange station together as one badge.
## Avoid when
- Do not force badges or short labels to carry complex explanations that need sentence-level context.
- The value is a generic status or count rather than a transit line; use badge instead.
## Anti-patterns
- Encoding complex meaning in a badge alone
## Rules
- Use the `<brok:transport-badge>` tag (or `<x-ui.transport-badge>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/transport-badge
- Registry JSON (files, props, contract): https://brokui.dev/r/open/transport-badge.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
Interchange
<div class="flex flex-wrap items-center gap-4">
<x-ui.transport-badge system="SG" :station-code="['NS26', 'EW14']" :station-name="__('Raffles Place')" :show-station-name="true" />
<x-ui.transport-badge system="SG" :station-code="['NS24', 'NE6', 'CC1']" :station-name="__('Dhoby Ghaut')" :show-station-name="true" size="sm" />
</div>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| system | string | SG | Transit system whose line colors and label apply, such as SG (Singapore) or HK (Hong Kong). |
| stationCode | mixed | null | null | One line code, or an array of codes for an interchange station. |
| stationName | mixed | null | null | Station name used in the accessible label and shown when showStationName is true. |
| showStationName | bool | false | Shows the station name as visible text next to the code chips. |
| size | md | xs | sm | lg | md | Chip size: xs, sm, md, or lg. |
| colorMode | string | semantic | semantic uses theme tokens for every chip; brand uses each line's official transit color. |
Slots
default—
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- colorMode selects semantic, theme-driven color, or brand, each line's official transit color, for every code's chip.
- Each code's color is resolved from its leading letters against the system's line map, falling back to a neutral chip when the code is unknown.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Communicate concise status, count, or metadata.
Use when
- Use for compact labels, counts, and concise metadata that support scanning.
- Showing a transit line or station code with its official identity, such as in a directions list or station card.
- Displaying several line codes for an interchange station together as one badge.
Avoid when
- Do not force badges or short labels to carry complex explanations that need sentence-level context.
- The value is a generic status or count rather than a transit line; use badge instead.
Use instead
- Visible explanatory text
Anti-patterns
- Encoding complex meaning in a badge alone
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The root carries role=img and an aria-label combining the system name with the station name or codes, since the chips themselves are aria-hidden.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Livewire can update this component through forwarded wire:* attributes.
Source
The exact, editable files ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
'system' => 'SG',
'stationCode' => null,
'stationName' => null,
'showStationName' => false,
'size' => 'md',
'colorMode' => 'semantic',
])
@php
// Official transit line colours are available as an explicit brand mode.
// Semantic mode remains the default so global theme changes affect the badge.
$systems = [
'SG' => [
'label' => 'Singapore MRT/LRT',
'lines' => [
'NS' => 'bg-transport-sg-ns', 'EW' => 'bg-transport-sg-ew', 'CG' => 'bg-transport-sg-ew',
'NE' => 'bg-transport-sg-ne', 'CC' => 'bg-transport-sg-cc', 'CE' => 'bg-transport-sg-cc',
'DT' => 'bg-transport-sg-dt', 'TE' => 'bg-transport-sg-te',
'BP' => 'bg-transport-sg-lrt', 'SK' => 'bg-transport-sg-lrt', 'PG' => 'bg-transport-sg-lrt',
],
],
'HK' => [
'label' => 'Hong Kong MTR',
'lines' => [
'TW' => 'bg-transport-hk-tw', 'KT' => 'bg-transport-hk-kt', 'IS' => 'bg-transport-hk-is',
'TK' => 'bg-transport-hk-tk', 'TC' => 'bg-transport-hk-tc', 'AE' => 'bg-transport-hk-ae',
'TM' => 'bg-transport-hk-tm', 'EA' => 'bg-transport-hk-ea', 'SI' => 'bg-transport-hk-si',
'DR' => 'bg-transport-hk-dr',
],
],
];
$sizes = [
'xs' => 'h-5 min-w-5 px-1 text-[0.625rem]',
'sm' => 'h-6 min-w-6 px-2 text-xs',
'md' => 'h-7 min-w-7 px-2 text-sm',
'lg' => 'h-9 min-w-9 px-2 text-base',
];
$sys = $systems[$system] ?? $systems['SG'];
$fallback = 'bg-transport-fallback';
$sizeCls = $sizes[$size] ?? $sizes['md'];
$useBrandColour = $colorMode === 'brand';
$codes = is_array($stationCode)
? $stationCode
: ($stationCode !== null && $stationCode !== '' ? [$stationCode] : []);
$lineClass = static function (string $code) use ($sys, $fallback): string {
preg_match('/^[A-Za-z]+/', $code, $m);
$key = strtoupper($m[0] ?? '');
return $sys['lines'][$key] ?? $fallback;
};
$aria = $stationName ?: implode(' ', $codes);
@endphp
<span
data-slot="transport-badge"
data-system="{{ $system }}"
data-color-mode="{{ $useBrandColour ? 'brand' : 'semantic' }}"
role="img"
aria-label="{{ trim($sys['label'].' '.$aria) }}"
{{ $attributes->merge(['class' => 'inline-flex items-center gap-2 align-middle']) }}
>
<span class="inline-flex items-center gap-1" aria-hidden="true">
@foreach ($codes as $code)
<span
class="inline-flex items-center justify-center rounded-md font-semibold tabular-nums text-primary-foreground {{ $useBrandColour ? $lineClass($code) : 'bg-primary' }} {{ $sizeCls }}"
>{{ $code }}</span>
@endforeach
</span>
@if ($showStationName && $stationName)
<span class="font-medium text-foreground">{{ $stationName }}</span>
@endif
</span>
/* Domain-owned transport palette. This file is installed only with transport-badge. */
@theme inline {
--color-transport-sg-ns: hsl(var(--transport-sg-ns));
--color-transport-sg-ew: hsl(var(--transport-sg-ew));
--color-transport-sg-ne: hsl(var(--transport-sg-ne));
--color-transport-sg-cc: hsl(var(--transport-sg-cc));
--color-transport-sg-dt: hsl(var(--transport-sg-dt));
--color-transport-sg-te: hsl(var(--transport-sg-te));
--color-transport-sg-lrt: hsl(var(--transport-sg-lrt));
--color-transport-hk-tw: hsl(var(--transport-hk-tw));
--color-transport-hk-kt: hsl(var(--transport-hk-kt));
--color-transport-hk-is: hsl(var(--transport-hk-is));
--color-transport-hk-tk: hsl(var(--transport-hk-tk));
--color-transport-hk-tc: hsl(var(--transport-hk-tc));
--color-transport-hk-ae: hsl(var(--transport-hk-ae));
--color-transport-hk-tm: hsl(var(--transport-hk-tm));
--color-transport-hk-ea: hsl(var(--transport-hk-ea));
--color-transport-hk-si: hsl(var(--transport-hk-si));
--color-transport-hk-dr: hsl(var(--transport-hk-dr));
--color-transport-fallback: hsl(var(--transport-fallback));
}
:root,
.dark {
--transport-sg-ns: 0.3 76.4% 51.8%;
--transport-sg-ew: 151.2 100% 29.4%;
--transport-sg-ne: 293.1 39.7% 42.9%;
--transport-sg-cc: 33.8 94.6% 56.3%;
--transport-sg-dt: 206.4 100% 32.9%;
--transport-sg-te: 26.7 61% 38.2%;
--transport-sg-lrt: 123.2 7.8% 48%;
--transport-hk-tw: 7.9 74.6% 47.8%;
--transport-hk-kt: 128.9 47% 42.2%;
--transport-hk-is: 203.8 100% 38%;
--transport-hk-tk: 277.1 36.5% 45.1%;
--transport-hk-tc: 27.9 92% 60.6%;
--transport-hk-ae: 185.7 42.4% 38.8%;
--transport-hk-tm: 20.2 82.4% 31.2%;
--transport-hk-ea: 200.6 71.3% 63.1%;
--transport-hk-si: 63.9 65.4% 46.5%;
--transport-hk-dr: 330 76.7% 71.4%;
--transport-fallback: 0 0% 35.7%;
}
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.3 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0