Foundation

URBS Card Recharge
Design System

Three-layer token architecture for a 1920×1080 light / split-theme touchscreen kiosk. Complete color palette (green primary, teal accent), typography, spacing, and 11 production-ready screens.

1920 × 1080 light theme WCAG 2.1 AA PT / EN / ES React 19 + Vite Style Dictionary compatible
Foundation

Colors

Three-layer token architecture: raw hex values → semantic roles → component-specific assignments. Light background with green primary (#2ECE78) and teal accent (#00B4AE).

Primitive Raw palette — never used directly in components

green-400
#2ECE78
URBS primary
green-600
#25A862
primary-dark
teal-500
#00B4AE
accent
aqua-100
#B1FFFA
URBS wordmark
navy-900
#1A2332
body text
navy-800
#1C2A3A
dark panel
slate-600
#526679
slate-500
#6B7B8D
text-secondary
slate-400
#9EAAB7
text-muted
slate-300
#C8CDD5
border-strong
slate-200
#E5E8ED
border
grey-100
#F0F2F5
bg
white
#FFFFFF
surface
red-500
#E5484D
error
amber-500
#F5A623
warning

Semantic Role-based tokens — use these in layouts

bg
--color-bg
Page background
surface
--color-surface
Card / panel
surface-raised
--color-surface-raised
Elevated card
surface-sunken
--color-surface-sunken
Inset well
dark-panel
--color-dark-panel
Kiosk dark split
text
--color-text
Primary copy
text-secondary
--color-text-secondary
Body secondary
text-muted
--color-text-muted
Labels / hints
border
--color-border
Dividers
border-strong
--color-border-strong
Focused dividers

Component State tokens — used inside component styles

primary
Affirmative CTA
primary-dark
CTA pressed
accent
Highlight / links
success
Positive feedback
error
Destructive / errors
warning
Caution
Foundation

Typography

Single typeface: Outfit. Variable weight from 300 to 800. Self-hosted WOFF2, latin subset. All sizes are pixel-perfect for the 1920×1080 viewport. Minimum 14px for secondary labels.

Display
52px · fw:800 · tracking -1.5px
Recharge your card
H1
32px · fw:800 · tracking -0.8px
Select an amount
H2
24px · fw:700
Choose a payment method
H3
18px · fw:600
Available balance
Body
15px · fw:400 · lh:1.6
Follow the on-screen steps to complete your recharge. Tap continue at any time.
Small / Label
14px · fw:500
Transaction started at 08:14 · terminal 04
Foundation

Spacing

4-point base grid. All spacing values are multiples of 4px. Minimum touch target size: 48×48px (WCAG 2.5.5). Screen padding: 64px.

Spacing scale

space-1
4px
space-xs
6px
space-2
8px
space-3
12px
space-4
16px
space-6
24px
space-8
32px
space-12
48px
space-16
64px
Base unit
4px
Smallest
space-1 · 4px
Largest
space-16 · 64px
Touch target (min)
48 × 48px · WCAG 2.5.5
Screen padding
64px
Bar color
#2ECE78
Foundation

Radius & Borders

Corner radius scale from 6px (smallest) to 16px (cards). Border widths: 1px (subtle), 1.5px (balance pill), 2px (interactive), 3px (selected state).

xs
6px
sm
8px
md
10px
lg
12px
xl
14px
2xl
16px
full
pill

Border weights

subtle
1px
card outlines
balance
1.5px
balance pill
interactive
2px
buttons, inputs
selected
3px
selected card state
Foundation

Shadows

Subtle elevation system. Cards use minimal shadows. Selected state adds accent glow: 0 4px 20px rgba(46,206,120,0.12).

Elevation scale

xs
--shadow-xs
sm
--shadow-sm
md
--shadow-md
lg
--shadow-lg
Stage BG
#FFFFFF
Card BG
#FFFFFF
Card radius
12px
xs
0 1px 2px rgba(10,20,30,0.06)
sm
0 2px 6px rgba(10,20,30,0.08)
md
0 6px 14px rgba(10,20,30,0.10)
lg
0 10px 28px rgba(10,20,30,0.12)
Selected glow
0 4px 20px rgba(46,206,120,0.12)
Assets

Iconography

Font Awesome 6 Free (Solid) — the exact set the kiosk ships with. Inline SVGs inheriting currentColor. All 18 glyphs match the production app 1:1. Sized 12–48px, paired with visible labels on all primary controls.

Kiosk Product iconography — 18 glyphs shipped in the totem

Kiosk glyphs

credit-card
bus
arrow-right
arrow-left
check
circle-check
triangle-exclamation
pen
delete-left
money-bill
microchip
wifi
magnet
spinner
globe
hand-pointer
rotate-left
right-from-bracket
urbs-logo
credit-card
bus
arrow-right
arrow-left
check
circle-check
triangle-exclamation
pen
delete-left
money-bill
microchip
wifi
magnet
spinner
globe
hand-pointer
rotate-left
right-from-bracket
urbs-logo
Size
24px
Light color
#1A2332
Dark color
#FFFFFF
Stroke
fill=currentColor

DS site Docs chrome — not used inside the kiosk product

sun
moon
menu
close
search
github
Components

Buttons

Three variants × three sizes. Primary (green #2ECE78) for affirmative actions, secondary for alternatives, ghost for lower emphasis.

Variants

Primary BG
#2ECE78
Primary Color
#FFFFFF · fw:700
Secondary Border
2px solid #C8CDD5
Ghost Border
2px solid transparent
Radius
10–14px
Icon size
18px · arrow-right / arrow-left

Sizes

sm height
44px
sm padding
0 20px
sm font
14px / r:10
md height
52px
md padding
0 28px
md font
16px / r:12
lg height
60px
lg padding
0 36px
lg font
18px / r:14
Usage
---
import CTAButton from '@/components/kiosk/CTAButton.astro';
import KioskIcon from '@/components/icons/KioskIcon.astro';
---

{/* Variants — primary / secondary / ghost */}
<CTAButton variant="primary" size="lg">
  Continue
  <KioskIcon slot="trailing" name="arrow-right" size={18} />
</CTAButton>

<CTAButton variant="secondary" size="lg">
  <KioskIcon slot="leading" name="arrow-left" size={18} />
  Back
</CTAButton>

<CTAButton variant="ghost" size="lg">Cancel</CTAButton>

{/* Sizes — sm (44) · md (52) · lg (60) */}
<CTAButton variant="primary" size="sm">Small</CTAButton>
<CTAButton variant="primary" size="md">Medium</CTAButton>
<CTAButton variant="primary" size="lg">Large</CTAButton>

{/* Stretch to container */}
<CTAButton variant="primary" size="lg" fullWidth>
  Confirm payment
</CTAButton>
Props
  • variant
    Type
    'primary' | 'secondary' | 'ghost'
    Default
    'primary'
    Desc
    Visual treatment — primary (green fill), secondary (outlined), ghost (text-only).
  • size
    Type
    'sm' | 'md' | 'lg'
    Default
    'md'
    Desc
    Height + padding + text scale (44 / 52 / 60).
  • type
    Type
    'button' | 'submit' | 'reset'
    Default
    'button'
    Desc
    Native button `type` attribute.
  • label
    Type
    string
    Default
    Desc
    Accessible label for icon-only or ambiguous buttons (maps to `aria-label`).
  • fullWidth
    Type
    boolean
    Default
    false
    Desc
    Stretches the button to container width (mirrors the app `fullWidth` prop).
  • class
    Type
    string
    Default
    Desc
    Extra classes appended to the root element.
  • slot: leading slot
    Type
    Default
    Desc
    Leading icon slot.
  • slot: (default) slot
    Type
    Default
    Desc
    Button label / children.
  • slot: trailing slot
    Type
    Default
    Desc
    Trailing icon slot.
Components

TopBar

Position: fixed top, z-index 100, transparent background. LEFT: URBS logo + balance pill. RIGHT: language buttons (PT/EN/ES) + exit button. Light surface for most screens, dark surface for full-dark states (ScreenSaver / ExitRemoveCard).

TopBar surfaces

URBS
Balance R$ 50.00
URBS
Balance R$ 50.00
Position
fixed top 0 · z-100
Padding
clamp 14-24px · 20-48px
Background
transparent (on screen BG)
Logo
urbs-logo · h:44px
Balance label
"Available balance" · uppercase 11-13px
Balance value
15-22px · fw:800 · tabular-nums
Lang button (active)
bg #00B4AE · fw:700 · no border
Lang button size
min-h:48px · radius 10
Lang inactive (light)
bg white 92% · 1.5px #C8CDD5
Lang inactive (dark)
bg white 10% · 1.5px white 25%
Exit button
bg #1A2332 · white text · fw:700
Exit icon
right-from-bracket · 12-17px
Dark-surface note
Exit hidden on ScreenSaver · ExitRemoveCard
Usage
---
import KioskButton from '@/components/kiosk/Button.astro';
import KioskIcon   from '@/components/icons/KioskIcon.astro';
---

{/* Light surface (TopBar on most screens) */}
<KioskButton variant="primary" size="md" label="Exit">
  Exit
  <KioskIcon slot="trailing" name="right-from-bracket" size={16} />
</KioskButton>

{/* Dark surface (ScreenSaver / ExitRemoveCard wrapper) */}
<KioskButton variant="primary-dark" size="md" label="Exit">
  Exit
  <KioskIcon slot="trailing" name="right-from-bracket" size={16} />
</KioskButton>

{/* Secondary action — ghost on light / dark surfaces */}
<KioskButton variant="ghost-light" size="md">Skip</KioskButton>
<KioskButton variant="ghost-dark"  size="md">Skip</KioskButton>
Props
  • variant
    Type
    'primary' | 'primary-dark' | 'ghost-light' | 'ghost-dark'
    Default
    'primary'
    Desc
    Navy fill for TopBar/Exit; dark variant adds a subtle border to read over dark panels.
  • size
    Type
    'sm' | 'md'
    Default
    'md'
    Desc
    md = kiosk full (min-h touch 48px · 17px text) · sm = compact preview (40px · 14px).
  • type
    Type
    'button' | 'submit' | 'reset'
    Default
    'button'
    Desc
    Native button `type` attribute.
  • label
    Type
    string
    Default
    Desc
    Accessible label (maps to `aria-label`).
  • class
    Type
    string
    Default
    Desc
    Extra classes appended to the root element.
  • slot: leading slot
    Type
    Default
    Desc
    Leading icon slot.
  • slot: (default) slot
    Type
    Default
    Desc
    Button label / children.
  • slot: trailing slot
    Type
    Default
    Desc
    Trailing icon slot.
Components

Balance Pill

TopBar indicator that appears once the user's card is read. Two value states: OK (green) and Low (red, below R$ 4.70 — the ticket price). Ships on both light screens and the dark-panel screens (onboarding, value-selection, screen-saver) — flip the stage above to compare.

Balance states

Balance R$ 50.00
Balance R$ 2.50
Low balance
Balance R$ 50.00
Balance R$ 2.50
Low balance
BG light
rgba(255,255,255,0.95)
BG dark
rgba(255,255,255,0.06)
Border light
1.5px solid #E5E8ED
Border dark
1.5px solid rgba(255,255,255,0.15)
Radius
10px
Label
11px · 500 · #6B7B8D
Value OK
18px · 800 · #2ECE78
Value Low
18px · 800 · #E5484D
Usage
---
import BalancePill from '@/components/kiosk/BalancePill.astro';
---

{/* OK state — above ticket price (R$ 4.70) */}
<BalancePill value="R$ 50.00" surface="light" state="ok" size="md" />
<BalancePill value="R$ 50.00" surface="dark"  state="ok" size="md" />

{/* Low state — below ticket price, app renders a warning alongside */}
<BalancePill value="R$ 2.50" surface="light" state="low" size="md" />
<BalancePill value="R$ 2.50" surface="dark"  state="low" size="md" />

{/* Custom label (defaults to "Available balance") */}
<BalancePill label="Saldo"  value="R$ 50.00" surface="light" />
Props
  • value required
    Type
    string
    Default
    Desc
    Pre-formatted monetary value — e.g. `"R$ 50.00"`. Uses tabular-nums so digits align.
  • label
    Type
    string
    Default
    'Available balance'
    Desc
    Uppercase label above the value. Kept short; truncation is consumer responsibility.
  • surface
    Type
    'light' | 'dark'
    Default
    'light'
    Desc
    Background the pill sits on — controls the inactive border + backdrop blur.
  • state
    Type
    'ok' | 'low'
    Default
    'ok'
    Desc
    ok → green value; low → red value (caller renders the "Low balance" warning alongside).
  • size
    Type
    'sm' | 'md'
    Default
    'md'
    Desc
    md = kiosk full (label 13 · value 22) · sm = compact preview (label 11 · value 18).
  • class
    Type
    string
    Default
    Desc
    Extra classes appended to the root element.
Components

Cards

White surface cards with a 2px border and an embedded 64×64 icon tile. Default: 2px #E5E8ED. Selected: 3px #2ECE78 + green glow shadow and a floating check badge. Ships on the PaymentMethod screen, where two cards are pinned to a fixed pixel height so they stay aligned side by side.

Payment method

Cash
Insert banknotes
Card
Tap contactless card
BG
#FFFFFF
Border default
2px solid #E5E8ED
Border selected
3px solid #2ECE78
Radius
12px
Height
260px (pinned for side-by-side alignment)
Icon tile
64×64 · radius 12 · bg #F0F2F5 / rgba(46,206,120,0.12)
Icon glyph
money-bill · credit-card · 28px
Title · Subtitle
28px/700 · 14px/regular
Shadow default
0 2px 8px rgba(0,0,0,0.03)
Glow selected
0 4px 20px rgba(46,206,120,0.12)
Check badge
32px circle, #2ECE78
Ships on
PaymentMethod screen
Usage
---
import Card from '@/components/kiosk/Card.astro';
import KioskIcon from '@/components/icons/KioskIcon.astro';
---

{/* Default · Selected — height auto (grows with content) */}
<Card variant="default">
  <KioskIcon slot="icon" name="money-bill" size={28} class="text-fg-muted" />
  Cash
  <span slot="subtitle">Insert banknotes</span>
</Card>

<Card variant="selected">
  <KioskIcon slot="icon" name="credit-card" size={28} class="text-primary" />
  Card
  <span slot="subtitle">Tap contactless card</span>
  <KioskIcon slot="check-icon" name="check" size={14} />
</Card>

{/* PaymentMethod case — two cards pinned at 260px so copy of
    different lengths still aligns side by side */}
<Card variant="default" height={260}>
  <KioskIcon slot="icon" name="money-bill" size={28} class="text-fg-muted" />
  Cash
  <span slot="subtitle">Insert banknotes</span>
</Card>

<Card variant="selected" height={260}>
  <KioskIcon slot="icon" name="credit-card" size={28} class="text-primary" />
  Card
  <span slot="subtitle">Tap contactless card</span>
  <KioskIcon slot="check-icon" name="check" size={14} />
</Card>

{/* Consumer pattern — wrap in a <button> for radio-style selection */}
<button type="button" role="radio" aria-checked={selected} onClick={handleSelect}>
  <Card variant={selected ? 'selected' : 'default'} height={260}>
    <KioskIcon slot="icon" name="credit-card" size={28} />
    Card
    <span slot="subtitle">Tap contactless</span>
  </Card>
</button>
Props
  • variant
    Type
    'default' | 'selected'
    Default
    'default'
    Desc
    default → 2px border + soft shadow · selected → 3px primary border + green-glow shadow + check badge.
  • height
    Type
    number | 'auto'
    Default
    'auto'
    Desc
    auto → grows with content · number → pinned pixel height (e.g. 260 for PaymentMethod's aligned pair).
  • class
    Type
    string
    Default
    Desc
    Extra classes appended to the root element.
  • slot: icon slot
    Type
    Default
    Desc
    Icon glyph, consumer-sized (~28px to match the kiosk). Card renders the 64×64 tile wrapper automatically.
  • slot: (default) slot
    Type
    Default
    Desc
    Card title.
  • slot: subtitle slot
    Type
    Default
    Desc
    Secondary copy under the title.
  • slot: check-icon slot
    Type
    Default
    Desc
    Custom check icon for the selected-state badge (defaults to an inline SVG).
Components

Numpad + Ticket Presets

Right panel of the ValueSelection screen. Ticket preset rows + 3×4 numpad on dark panel background (rgba(28,42,58,0.98)). Keys have subtle white borders; delete key in red. Full-width layout.

ValueSelection — right panel (dark)

+1 Ticket R$ 4.70
+2 Tickets R$ 9.40
+5 Tickets R$ 23.50
+10 Tickets R$ 47.00
✎ Custom amount
1
2
3
4
5
6
7
8
9
,
0
Key height
56px
Key BG
rgba(28,42,58,0.85)
Key border
1px solid rgba(255,255,255,0.15)
Key radius
8px
Key font
22px w500 #FFFFFF
Delete BG
rgba(229,72,77,0.7)
Delete border
1px solid rgba(229,72,77,0.85)
Grid gap
8px
Preset default BG
rgba(28,42,58,0.85)
Preset sel BG
rgba(46,206,120,0.25)
Preset sel border
1px solid rgba(46,206,120,0.5)
Preset price
#2ECE78
Components

Processing Steps

Four sequential steps with three visually distinct states: done (light green bg + green check), active (light teal bg + teal spinner), pending (white bg + gray dot + opacity 0.4). Real labels from TotemContext.tsx. Matches Processing.tsx in production.

Step states

Verifying payment
Connecting to URBS server
Crediting balance to card
Finalizing transaction
Done BG
rgba(46,206,120,0.04)
Done border
1px rgba(46,206,120,0.15)
Active BG
rgba(0,180,174,0.06)
Active border
1px rgba(0,180,174,0.2)
Pending BG
#FFFFFF · opacity:0.4
Pending border
1px #E5E8ED
Padding
10px 20px
Radius
8px
Label
15px · active:600 / else:400
Icons
circle-check · spinner · 16px
Components

Transaction Feed

Live banknote log on dark panel. Items show icon (green check), label, and amount. Max 6 visible, animated entry from top.

Transaction items

Banknote accepted +R$ 50.00
Banknote accepted +R$ 20.00
Banknote accepted +R$ 10.00
Item BG
rgba(28,42,58,0.06)
Item border
1px rgba(0,180,174,0.15)
Item radius
8px
Icon color
#2ECE78
Amount color
#2ECE78 · fw:700
Animation
y: -10→0, opacity 0→1
Components

Language Buttons

Three languages: PT / EN / ES. Active state: teal bg, white text + flag. Inactive: subtle bg with border. The app renders light variant on TopBar and dark variant on ScreenSaver / Onboarding (right-panel) contexts — flip the stage to compare.

Language selector

Active BG
#00B4AE
Active color
#FFFFFF · fw:700
Inactive BG light
rgba(255,255,255,0.92)
Inactive BG dark
rgba(255,255,255,0.10)
Inactive color light
#526679 · fw:500
Inactive color dark
rgba(255,255,255,0.65)
Border light
1.5px solid #C8CDD5
Border dark
1.5px solid rgba(255,255,255,0.25)
Radius
10px
Flag
22×16 · rounded 3px
Touch target
≥ 48px height (kiosk)
Usage
---
import LanguageButton from '@/components/kiosk/LanguageButton.astro';
---

{/* Light surface (TopBar) — active = current language */}
<LanguageButton code="pt" active surface="light" size="md" />
<LanguageButton code="en"        surface="light" size="md" />
<LanguageButton code="es"        surface="light" size="md" />

{/* Dark surface (ScreenSaver · Onboarding right-panel) */}
<LanguageButton code="pt" active surface="dark" size="md" />
<LanguageButton code="en"        surface="dark" size="md" />
<LanguageButton code="es"        surface="dark" size="md" />

{/* Compact preview (docs / mobile) — size sm */}
<LanguageButton code="pt" active surface="light" size="sm" />
Props
  • code required
    Type
    'pt' | 'en' | 'es'
    Default
    Desc
    Language this button represents. Drives label (PT/EN/ES) and flag asset.
  • active
    Type
    boolean
    Default
    false
    Desc
    True when this is the currently-selected language (teal fill, white label, no border).
  • surface
    Type
    'light' | 'dark'
    Default
    'light'
    Desc
    Background the button sits on — controls the inactive styling.
  • size
    Type
    'sm' | 'md'
    Default
    'md'
    Desc
    md = kiosk full (1920px) · sm = DS preview / mobile.
  • class
    Type
    string
    Default
    Desc
    Extra classes appended to the root element.
Layouts

Screen Layouts

Three base templates sit on the Curitiba cityscape background. The split-layout and centered-layout stack semi-transparent white panels over the bg (so the city shows through at 8%); the dark-layout uses the same image under a solid navy overlay at 98% opacity. 1920×1080 base, scaled via Math.min(w/1920, h/1080). Idle timeout: 30s dev / 3min prod.

split-layout .split-layout
LEFT: white
rgba(255,255,255,0.92)
64px padding
RIGHT: dark navy
rgba(28,42,58,0.98)
solid 0.98 overlay
Used by: ValueSelection, CashInstructions, CardPayment, BanknoteDeposit
centered-layout .centered-layout
Full width white
rgba(255,255,255,0.92)
flex centered
Used by: HomeBalance, PaymentMethod, Confirmation, Processing
dark-layout .dark-layout
Full width dark navy
rgba(28,42,58,0.98)
flex centered · solid 0.98 overlay
Used by: ScreenSaver, ExitRemoveCard
Scaling & viewport
Math.min fill — preserve aspect
Base: 1920 × 1080px
Scale: Math.min(w/1920, h/1080)
BG image: /images/kiosk-bg.webp (Curitiba skyline)
TopBar: position:fixed above viewport
Padding: 64px all sides
Tokens

Token Export

Style Dictionary–compatible JSON and CSS custom properties. All values match the production app (urbs-card-recharge-totem.vercel.app).

Style Dictionary JSON
{
  "color": {
    "primary":        { "value": "#2ECE78" },
    "primary-dark":   { "value": "#25A862" },
    "accent":         { "value": "#00B4AE" },
    "bg":             { "value": "#F0F2F5" },
    "surface":        { "value": "#FFFFFF" },
    "dark-panel":     { "value": "#1C2A3A" },
    "text":           { "value": "#1A2332" },
    "text-secondary": { "value": "#526679" },
    "text-muted":     { "value": "#5E6F81" },
    "border":         { "value": "#E5E8ED" },
    "success":        { "value": "#2ECE78" },
    "error":          { "value": "#E5484D" },
    "warning":        { "value": "#F5A623" }
  },
  "spacing": {
    "1":  { "value": "4px"  },
    "2":  { "value": "8px"  },
    "3":  { "value": "12px" },
    "4":  { "value": "16px" },
    "6":  { "value": "24px" },
    "8":  { "value": "32px" },
    "12": { "value": "48px" },
    "16": { "value": "64px" }
  },
  "radius": {
    "xs":   { "value": "6px"   },
    "sm":   { "value": "8px"   },
    "md":   { "value": "10px"  },
    "lg":   { "value": "12px"  },
    "xl":   { "value": "14px"  },
    "2xl":  { "value": "16px"  },
    "full": { "value": "999px" }
  }
}
CSS custom properties
:root {
  --color-primary:        #2ECE78;
  --color-primary-dark:   #25A862;
  --color-accent:         #00B4AE;
  --color-bg:             #F0F2F5;
  --color-surface:        #FFFFFF;
  --color-dark-panel:     #1C2A3A;
  --color-text:           #1A2332;
  --color-text-secondary: #526679;
  --color-text-muted:     #5E6F81;
  --color-border:         #E5E8ED;

  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-6:  24px;
  --space-8:  32px;
  --space-12: 48px;
  --space-16: 64px;

  --radius-xs:   6px;
  --radius-sm:   8px;
  --radius-md:   10px;
  --radius-lg:   12px;
  --radius-xl:   14px;
  --radius-2xl:  16px;
  --radius-full: 999px;
}

Source of truth: rico-mello/urbs-design-systemsrc/styles/tokens.css