@lifesg/react-design-system 4.0.0-alpha.13 → 4.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/cjs/external/dompurify/dist/purify.es.js +4 -2
  2. package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
  3. package/cjs/modal-v2/slots/slot-styles.js +1 -1
  4. package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
  5. package/cjs/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
  6. package/cjs/notification-banner/notification-banner-hoc.js +1 -1
  7. package/cjs/notification-banner/notification-banner-hoc.js.map +1 -1
  8. package/external/dompurify/dist/purify.es.js +4 -2
  9. package/external/dompurify/dist/purify.es.js.map +1 -1
  10. package/modal-v2/slots/slot-styles.js +1 -1
  11. package/modal-v2/slots/slot-styles.js.map +1 -1
  12. package/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
  13. package/notification-banner/notification-banner-hoc.js +1 -1
  14. package/notification-banner/notification-banner-hoc.js.map +1 -1
  15. package/package.json +2 -2
  16. package/scripts/cli.js +18 -0
  17. package/skills/fds-build/SKILL.md +167 -0
  18. package/skills/fds-build/catalogue.md +181 -0
  19. package/skills/fds-build/evals/evals.json +519 -0
  20. package/skills/fds-build/resources/components/accordion.md +128 -0
  21. package/skills/fds-build/resources/components/alert.md +54 -0
  22. package/skills/fds-build/resources/components/animation.md +18 -0
  23. package/skills/fds-build/resources/components/avatar.md +10 -0
  24. package/skills/fds-build/resources/components/badge.md +18 -0
  25. package/skills/fds-build/resources/components/box-container.md +61 -0
  26. package/skills/fds-build/resources/components/breadcrumb.md +60 -0
  27. package/skills/fds-build/resources/components/button.md +74 -0
  28. package/skills/fds-build/resources/components/calendar.md +113 -0
  29. package/skills/fds-build/resources/components/card.md +43 -0
  30. package/skills/fds-build/resources/components/checkbox.md +53 -0
  31. package/skills/fds-build/resources/components/countdown-timer.md +20 -0
  32. package/skills/fds-build/resources/components/data-table.md +142 -0
  33. package/skills/fds-build/resources/components/date-navigator.md +25 -0
  34. package/skills/fds-build/resources/components/divider.md +23 -0
  35. package/skills/fds-build/resources/components/drawer.md +78 -0
  36. package/skills/fds-build/resources/components/error-display.md +137 -0
  37. package/skills/fds-build/resources/components/feedback-rating.md +61 -0
  38. package/skills/fds-build/resources/components/file-download.md +54 -0
  39. package/skills/fds-build/resources/components/file-upload.md +105 -0
  40. package/skills/fds-build/resources/components/filter.md +228 -0
  41. package/skills/fds-build/resources/components/footer.md +94 -0
  42. package/skills/fds-build/resources/components/form-date-input.md +85 -0
  43. package/skills/fds-build/resources/components/form-date-range-input.md +146 -0
  44. package/skills/fds-build/resources/components/form-e-signature.md +55 -0
  45. package/skills/fds-build/resources/components/form-input.md +65 -0
  46. package/skills/fds-build/resources/components/form-masked-input.md +69 -0
  47. package/skills/fds-build/resources/components/form-multi-select.md +100 -0
  48. package/skills/fds-build/resources/components/form-nested-multi-select.md +159 -0
  49. package/skills/fds-build/resources/components/form-nested-select.md +151 -0
  50. package/skills/fds-build/resources/components/form-otp-verification.md +175 -0
  51. package/skills/fds-build/resources/components/form-phone-number-input.md +87 -0
  52. package/skills/fds-build/resources/components/form-predictive-text-input.md +108 -0
  53. package/skills/fds-build/resources/components/form-select.md +103 -0
  54. package/skills/fds-build/resources/components/form-slider.md +116 -0
  55. package/skills/fds-build/resources/components/form-textarea.md +116 -0
  56. package/skills/fds-build/resources/components/form-time-range-picker.md +98 -0
  57. package/skills/fds-build/resources/components/form-timepicker.md +77 -0
  58. package/skills/fds-build/resources/components/form-unit-number-input.md +138 -0
  59. package/skills/fds-build/resources/components/fullscreen-image-carousel.md +159 -0
  60. package/skills/fds-build/resources/components/histogram-slider.md +41 -0
  61. package/skills/fds-build/resources/components/icon.md +167 -0
  62. package/skills/fds-build/resources/components/image-button.md +12 -0
  63. package/skills/fds-build/resources/components/input.md +51 -0
  64. package/skills/fds-build/resources/components/language-switcher.md +15 -0
  65. package/skills/fds-build/resources/components/layout.md +156 -0
  66. package/skills/fds-build/resources/components/link-list.md +120 -0
  67. package/skills/fds-build/resources/components/local-nav.md +119 -0
  68. package/skills/fds-build/resources/components/masthead.md +14 -0
  69. package/skills/fds-build/resources/components/menu.md +109 -0
  70. package/skills/fds-build/resources/components/modal.md +150 -0
  71. package/skills/fds-build/resources/components/navbar.md +270 -0
  72. package/skills/fds-build/resources/components/notification-banner.md +106 -0
  73. package/skills/fds-build/resources/components/pagination.md +104 -0
  74. package/skills/fds-build/resources/components/pill.md +55 -0
  75. package/skills/fds-build/resources/components/popover.md +80 -0
  76. package/skills/fds-build/resources/components/progress-indicator.md +47 -0
  77. package/skills/fds-build/resources/components/radio-button.md +66 -0
  78. package/skills/fds-build/resources/components/schedule.md +78 -0
  79. package/skills/fds-build/resources/components/sidenav.md +183 -0
  80. package/skills/fds-build/resources/components/singpass-button.md +14 -0
  81. package/skills/fds-build/resources/components/smart-app-banner.md +28 -0
  82. package/skills/fds-build/resources/components/tab.md +107 -0
  83. package/skills/fds-build/resources/components/table.md +65 -0
  84. package/skills/fds-build/resources/components/tag.md +68 -0
  85. package/skills/fds-build/resources/components/text-list.md +44 -0
  86. package/skills/fds-build/resources/components/time-slot-bar-week.md +66 -0
  87. package/skills/fds-build/resources/components/time-slot-bar.md +57 -0
  88. package/skills/fds-build/resources/components/time-slot-week-view.md +70 -0
  89. package/skills/fds-build/resources/components/timeline.md +69 -0
  90. package/skills/fds-build/resources/components/timetable.md +123 -0
  91. package/skills/fds-build/resources/components/toast.md +72 -0
  92. package/skills/fds-build/resources/components/toggle.md +119 -0
  93. package/skills/fds-build/resources/components/typography.md +86 -0
  94. package/skills/fds-build/resources/components/uneditable-section.md +84 -0
  95. package/skills/fds-build/resources/foundations/layout-composition-patterns.md +53 -0
  96. package/skills/fds-build/resources/foundations/tokens.md +547 -0
  97. package/skills/fds-build/resources/patterns/states.md +300 -0
  98. package/skills/fds-build/resources/setup/setup.md +202 -0
  99. package/skills/fds-build/resources/templates/form.md +427 -0
  100. package/skills/fds-build/resources/templates/listing.md +435 -0
@@ -0,0 +1,547 @@
1
+ # Design Tokens
2
+
3
+ All tokens are imported from `@lifesg/react-design-system/theme`.
4
+
5
+ ```tsx
6
+ import {
7
+ Colour,
8
+ Spacing,
9
+ Radius,
10
+ Font,
11
+ } from "@lifesg/react-design-system/theme";
12
+ ```
13
+
14
+ Tokens are **plain CSS variable strings** scoped to `[data-fds-<theme>]` — use them directly in inline `style={{}}` props or as values in CSS Modules. They do not need to be called as functions and do not require `styled-components` or `useTheme`.
15
+
16
+ ```tsx
17
+ // Inline style
18
+ <div
19
+ style={{
20
+ background: Colour["bg-stronger"],
21
+ padding: Spacing["spacing-16"],
22
+ }}
23
+ />;
24
+
25
+ // CSS Modules
26
+ .container {
27
+ background: var(--fds-colour-bg-stronger);
28
+ padding: var(--fds-spacing-16);
29
+ }
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Anti-patterns
35
+
36
+ | Anti-pattern | Why it fails | Correct alternative |
37
+ | ----------------------------------------------------------------- | ------------------------------------------ | ----------------------------------------- |
38
+ | Using `Colour[...]` inside a `styled-components` template literal | `styled-components` is not installed in v4 | Use in `style={{}}` or CSS Modules |
39
+ | Hardcoding `var(--fds-*)` directly | FDS internal variable names may change | Use `Colour[...]` / `Spacing[...]` tokens |
40
+ | Hardcoding hex values | Breaks theming | Always use a Colour token |
41
+
42
+ ---
43
+
44
+ ## Overriding
45
+
46
+ ### In CSS
47
+
48
+ Override tokens by setting the same variables in your own CSS, scoped to the theme attribute:
49
+
50
+ ```css
51
+ [data-fds-theme="lifesg"] {
52
+ --fds-colour-primary-50: #1768be;
53
+ --fds-colour-text-error: #a04747;
54
+ }
55
+
56
+ /* Dark mode overrides */
57
+ [data-fds-theme="lifesg"][data-fds-theme-mode="dark"] {
58
+ --fds-colour-primary-50: #6cb4ff;
59
+ }
60
+ ```
61
+
62
+ Place overrides after the theme CSS import so they take precedence.
63
+
64
+ ### In JavaScript
65
+
66
+ Override CSS variables dynamically via the `style` prop on `ThemeProvider`:
67
+
68
+ ```tsx
69
+ <ThemeProvider
70
+ theme="lifesg"
71
+ style={{
72
+ "--fds-colour-bg-primary": "#1768be",
73
+ }}
74
+ />
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Colour
80
+
81
+ ### Quick decision
82
+
83
+ | I need a colour for… | Token |
84
+ | ---------------------------------------- | ------------------------------- |
85
+ | Page / card background | `Colour.bg` |
86
+ | Emphasis panel / CTA surface | `Colour["bg-strong"]` |
87
+ | Brand-tinted surface | `Colour["bg-primary-subtlest"]` |
88
+ | Hover state (custom interactive) | `Colour["bg-hover"]` |
89
+ | Selected state | `Colour["bg-selected"]` |
90
+ | Disabled background | `Colour["bg-disabled"]` |
91
+ | Modal / drawer backdrop | `Colour["overlay-strong"]` |
92
+ | Body copy, headings | `Colour.text` |
93
+ | Form labels, UI labels | `Colour["text-subtle"]` |
94
+ | Descriptions, helper text, metadata | `Colour["text-subtler"]` |
95
+ | Placeholders | `Colour["text-subtlest"]` |
96
+ | Active / brand text | `Colour["text-primary"]` |
97
+ | Link text | `Colour.hyperlink` |
98
+ | Text on dark backgrounds | `Colour["text-inverse"]` |
99
+ | Disabled text | `Colour["text-disabled"]` |
100
+ | Default border (inputs, cards, dividers) | `Colour.border` |
101
+ | Emphasis border | `Colour["border-strong"]` |
102
+ | Brand border / active indicator | `Colour["border-primary"]` |
103
+ | Focus ring | `Colour["border-focus"]` |
104
+ | Error border | `Colour["border-error"]` |
105
+ | Default icon | `Colour.icon` |
106
+ | Secondary / subtle icon | `Colour["icon-subtle"]` |
107
+ | Brand icon | `Colour["icon-primary"]` |
108
+ | Icon on dark backgrounds | `Colour["icon-inverse"]` |
109
+
110
+ ### Notation
111
+
112
+ Single-word tokens use dot notation: `Colour.text`, `Colour.border`, `Colour.bg`, `Colour.icon`
113
+
114
+ Hyphenated tokens use bracket notation: `Colour["text-subtle"]`, `Colour["bg-hover"]`
115
+
116
+ ### Usage
117
+
118
+ ```tsx
119
+ // Inline style — most common
120
+ <p style={{ color: Colour["text-subtle"] }}>Secondary text</p>;
121
+
122
+ // CSS Modules — assign token to a variable
123
+ import styles from "./Card.module.css";
124
+
125
+ const Card = () => (
126
+ <div
127
+ className={styles.card}
128
+ style={{
129
+ background: Colour["bg-stronger"],
130
+ borderColor: Colour.border,
131
+ }}
132
+ />
133
+ );
134
+ ```
135
+
136
+ DS components (Button, Form.Input, Card, etc.) handle their own colour states — only apply Colour tokens to custom elements.
137
+
138
+ ### Status tokens
139
+
140
+ | Status | Background | Text | Icon | Border |
141
+ | ------- | ------------ | -------------- | -------------- | ---------------- |
142
+ | Success | `bg-success` | `text-success` | `icon-success` | `border-success` |
143
+ | Warning | `bg-warning` | `text-warning` | `icon-warning` | `border-warning` |
144
+ | Error | `bg-error` | `text-error` | `icon-error` | `border-error` |
145
+ | Info | `bg-info` | `text-info` | `icon-info` | `border-info` |
146
+
147
+ All follow bracket notation: `Colour["bg-success"]`, `Colour["text-error"]`.
148
+
149
+ ### Full token reference
150
+
151
+ #### Text colours
152
+
153
+ | Token key | Use for |
154
+ | ------------------------ | ------------------------------------------ |
155
+ | `text` | Default body text |
156
+ | `text-subtle` | Secondary / supporting text |
157
+ | `text-subtler` | Tertiary / caption-like text |
158
+ | `text-subtlest` | Weakest text — muted labels, hints |
159
+ | `text-primary` | Brand-coloured text (links, active labels) |
160
+ | `text-hover` | Text colour on hover states |
161
+ | `text-selected` | Text in selected / active state |
162
+ | `text-selected-hover` | Text in selected state on hover |
163
+ | `text-disabled` | Disabled state text |
164
+ | `text-disabled-subtle` | Subtle disabled text |
165
+ | `text-disabled-subtlest` | Faintest disabled text |
166
+ | `text-selected-disabled` | Text in selected + disabled state |
167
+ | `text-success` | Success state text |
168
+ | `text-warning` | Warning state text |
169
+ | `text-error` | Inline validation error text |
170
+ | `text-info` | Informational state text |
171
+ | `text-inverse` | Text on dark / inverted backgrounds |
172
+
173
+ #### Icon colours
174
+
175
+ | Token key | Use for |
176
+ | ------------------------ | --------------------------------- |
177
+ | `icon` | Default icon fill |
178
+ | `icon-subtle` | Subdued / secondary icon |
179
+ | `icon-strongest` | Highest-contrast icon (very dark) |
180
+ | `icon-primary` | Brand-coloured icon |
181
+ | `icon-primary-subtle` | Muted brand icon |
182
+ | `icon-primary-subtlest` | Faintest brand icon |
183
+ | `icon-hover` | Icon on hover |
184
+ | `icon-selected` | Icon in selected state |
185
+ | `icon-selected-hover` | Icon in selected state on hover |
186
+ | `icon-disabled` | Disabled icon |
187
+ | `icon-disabled-subtle` | Subtle disabled icon |
188
+ | `icon-selected-disabled` | Icon in selected + disabled state |
189
+ | `icon-success` | Success state icon |
190
+ | `icon-warning` | Warning state icon |
191
+ | `icon-error` | Error state icon |
192
+ | `icon-error-strong` | Strong / filled error icon |
193
+ | `icon-info` | Informational icon |
194
+ | `icon-inverse` | Icon on dark background |
195
+ | `icon-primary-inverse` | Brand icon on inverted background |
196
+
197
+ #### Border colours
198
+
199
+ | Token key | Use for |
200
+ | -------------------------- | ------------------------------------------- |
201
+ | `border` | Default input / card / divider borders |
202
+ | `border-strong` | Stronger divider or emphasis border |
203
+ | `border-stronger` | Strongest border emphasis |
204
+ | `border-primary` | Brand-coloured border |
205
+ | `border-primary-subtle` | Muted brand-coloured border |
206
+ | `border-focus` | Focus ring on interactive elements |
207
+ | `border-focus-strong` | Strong focus ring (accessibility highlight) |
208
+ | `border-hover` | Border on hover |
209
+ | `border-hover-strong` | Strong border on hover |
210
+ | `border-selected` | Selected / active state border |
211
+ | `border-selected-subtle` | Subtle selected state border |
212
+ | `border-selected-subtlest` | Faintest selected state border |
213
+ | `border-selected-hover` | Selected state border on hover |
214
+ | `border-disabled` | Disabled state border |
215
+ | `border-selected-disabled` | Selected + disabled state border |
216
+ | `border-error` | Error state border |
217
+ | `border-error-focus` | Focus ring inside error state |
218
+ | `border-error-strong` | Strong error state border |
219
+ | `border-success` | Success state border |
220
+ | `border-warning` | Warning state border |
221
+ | `border-info` | Informational border |
222
+
223
+ #### Background colours
224
+
225
+ | Token key | Use for |
226
+ | ------------------------------ | ------------------------------------------- |
227
+ | `bg` | Default surface background |
228
+ | `bg-strong` | Slightly elevated surface |
229
+ | `bg-stronger` | Card / panel surface |
230
+ | `bg-strongest` | Highest elevation surface |
231
+ | `bg-hover` | Background on hover |
232
+ | `bg-hover-strong` | Strong hover background |
233
+ | `bg-hover-subtle` | Subtle hover background |
234
+ | `bg-hover-neutral` | Neutral hover (non-primary elements) |
235
+ | `bg-hover-neutral-strong` | Strong neutral hover background |
236
+ | `bg-selected` | Selected item background |
237
+ | `bg-selected-hover` | Selected item background on hover |
238
+ | `bg-selected-strong` | Strong selected state |
239
+ | `bg-selected-strongest` | Strongest selected fill |
240
+ | `bg-selected-strongest-hover` | Strongest selected fill on hover |
241
+ | `bg-disabled` | Disabled element background |
242
+ | `bg-selected-disabled` | Selected + disabled background |
243
+ | `bg-primary` | Brand-coloured fill |
244
+ | `bg-primary-subtle` | Subtle brand tint |
245
+ | `bg-primary-subtler` | Lighter brand tint |
246
+ | `bg-primary-subtlest` | Faintest brand tint |
247
+ | `bg-primary-hover` | Brand background on hover |
248
+ | `bg-primary-subtlest-hover` | Faintest brand tint on hover |
249
+ | `bg-primary-subtlest-selected` | Faintest brand tint in selected state |
250
+ | `bg-success` | Success state background |
251
+ | `bg-success-hover` | Success background on hover |
252
+ | `bg-success-strong` | Strong success fill (badges, chips) |
253
+ | `bg-success-strong-hover` | Strong success fill on hover |
254
+ | `bg-warning` | Warning state background |
255
+ | `bg-warning-hover` | Warning background on hover |
256
+ | `bg-warning-strong` | Strong warning fill |
257
+ | `bg-warning-strong-hover` | Strong warning fill on hover |
258
+ | `bg-error` | Error state background |
259
+ | `bg-error-hover` | Error background on hover |
260
+ | `bg-error-strong` | Strong error fill |
261
+ | `bg-error-strong-hover` | Strong error fill on hover |
262
+ | `bg-info` | Informational background |
263
+ | `bg-info-hover` | Informational background on hover |
264
+ | `bg-info-strong` | Strong informational fill |
265
+ | `bg-info-strong-hover` | Strong informational fill on hover |
266
+ | `bg-inverse` | Dark / inverted background |
267
+ | `bg-inverse-subtle` | Subtle inverted background |
268
+ | `bg-inverse-subtler` | Lighter inverted background |
269
+ | `bg-inverse-subtlest` | Faintest inverted background |
270
+ | `bg-inverse-hover` | Inverted background on hover |
271
+ | `bg-available` | Available / open slot (scheduling contexts) |
272
+ | `overlay-strong` | Modal / drawer backdrop |
273
+ | `overlay-subtle` | Soft overlay |
274
+
275
+ #### Hyperlink
276
+
277
+ | Token key | Use for |
278
+ | ------------------- | ----------------------- |
279
+ | `hyperlink` | Default link colour |
280
+ | `hyperlink-inverse` | Link on dark background |
281
+
282
+ #### Focus
283
+
284
+ | Token key | Use for |
285
+ | -------------------- | --------------------------------- |
286
+ | `focus-ring` | Keyboard focus outline colour |
287
+ | `focus-ring-inverse` | Focus outline on dark backgrounds |
288
+
289
+ ### Rules
290
+
291
+ - Never hardcode hex values — always use a Colour token
292
+ - Use a token that most closely matches the semantics e.g. `hyperlink` for link text — not `text-primary`
293
+ - DS components (Button, Form.Input, Card, etc.) handle their own colour states — only apply Colour tokens to custom elements
294
+
295
+ ---
296
+
297
+ ## Spacing
298
+
299
+ Base-4 system
300
+
301
+ ```tsx
302
+ <div style={{ gap: Spacing["spacing-16"], padding: Spacing["spacing-24"] }} />
303
+ ```
304
+
305
+ | Token | Value | Frequency | Typical use |
306
+ | ------------- | ----- | ------------ | --------------------------------------- |
307
+ | `spacing-0` | 0px | Rare | Reset / collapse |
308
+ | `spacing-4` | 4px | Rare | Tight inline gaps |
309
+ | `spacing-8` | 8px | **Common** | Icon-text gaps, heading-to-body gaps |
310
+ | `spacing-12` | 12px | Occasional | Tight component gaps |
311
+ | `spacing-16` | 16px | **Dominant** | List items, form fields, component gaps |
312
+ | `spacing-20` | 20px | Occasional | Medium-small spacing |
313
+ | `spacing-24` | 24px | **Dominant** | Card padding, section gaps |
314
+ | `spacing-32` | 32px | Common | Large spacing, section padding |
315
+ | `spacing-40` | 40px | Occasional | Extra-large spacing |
316
+ | `spacing-48` | 48px | Occasional | Page-level vertical rhythm |
317
+ | `spacing-64` | 64px | Rare | XXL spacing |
318
+ | `spacing-72` | 72px | Rare | Extra-large structural spacing |
319
+ | `layout-xs` | — | — | Layout grid gutter xs breakpoint |
320
+ | `layout-sm` | — | — | Layout grid gutter sm breakpoint |
321
+ | `layout-md` | — | — | Layout grid gutter md breakpoint |
322
+ | `layout-lg` | — | — | Layout grid gutter lg breakpoint |
323
+ | `layout-xl` | — | — | Layout grid gutter xl breakpoint |
324
+ | `layout-xxl` | — | — | Layout grid gutter xxl breakpoint |
325
+ | `layout-xxxl` | — | — | Layout grid gutter xxxl breakpoint |
326
+
327
+ Always bracket notation: `Spacing["spacing-16"]`
328
+
329
+ Never hardcode `px` values — always use a Spacing token.
330
+
331
+ ---
332
+
333
+ ## Radius
334
+
335
+ | Token | Value | Use |
336
+ | ------------- | ------ | ---------------------------------------------------- |
337
+ | `Radius.none` | 0px | Full-bleed, tables |
338
+ | `Radius.xs` | 2px | Rarely needed |
339
+ | `Radius.sm` | 4px | **Default** — form elements, small custom components |
340
+ | `Radius.md` | 8px | Cards, panels, overlays |
341
+ | `Radius.lg` | 12px | Modals, large overlays |
342
+ | `Radius.full` | 9999px | Pills, avatars, circular elements |
343
+
344
+ Always dot notation: `Radius.sm`, `Radius.md`
345
+
346
+ DS components have radius baked in — do not override.
347
+
348
+ ---
349
+
350
+ ## Typography
351
+
352
+ Use `Typography.*` components for all rendered text.
353
+
354
+ | Component | Default element | When to use |
355
+ | ------------------------ | --------------- | --------------------------------- |
356
+ | `Typography.HeadingXXL` | `h1` | Hero / landing page title |
357
+ | `Typography.HeadingXL` | `h2` | Page title (top of view) |
358
+ | `Typography.HeadingLG` | `h3` | Section heading — most common |
359
+ | `Typography.HeadingMD` | `h4` | Card heading, subsection |
360
+ | `Typography.HeadingSM` | `h5` | Small heading, list header |
361
+ | `Typography.HeadingXS` | `h6` | Tiny heading, emphasized label |
362
+ | `Typography.BodyBL` | `p` | Default body text, paragraphs |
363
+ | `Typography.BodyBLSmall` | `p` | Emphasized small body text |
364
+ | `Typography.BodyMD` | `p` | Medium body text |
365
+ | `Typography.BodySM` | `p` | Small text, captions, helper text |
366
+ | `Typography.BodyXS` | `p` | Metadata, timestamps, footnotes |
367
+
368
+ Use the `as` prop when visual size and semantic heading level don't match:
369
+
370
+ ```tsx
371
+ <Typography.HeadingLG as="h2">Section title</Typography.HeadingLG>
372
+ ```
373
+
374
+ Heading levels must be sequential (h1 → h2 → h3) — never skip levels.
375
+
376
+ ### Rules
377
+
378
+ - Sentence case for all UI text — "Save changes" not "Save Changes"
379
+ - Heading level follows reading order, not visual size
380
+
381
+ ### Font tokens
382
+
383
+ `Font.Spec[...]` tokens return CSS variable strings for individual font properties. Use `Typography.*` components for all text content in JSX — only use `Font` tokens when you need to apply font styles to a non-text container element.
384
+
385
+ ```tsx
386
+ // Match an icon size to adjacent text — use Font.Spec for raw values
387
+ <img style={{ height: Font.Spec["heading-size-xxl"] }} />
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Breakpoints / Media Queries
393
+
394
+ `ThemeProvider` automatically adds `fds-breakpoint-*` classes to `<body>` based on the current viewport width. Use these classes in CSS to apply responsive styles:
395
+
396
+ - `body.fds-breakpoint-{tier}` — viewport is within that tier's range
397
+ - `body.fds-breakpoint-{tier}-min` — viewport is at or above the tier's minimum
398
+ - `body.fds-breakpoint-{tier}-max` — viewport is at or below the tier's maximum
399
+
400
+ ```css
401
+ /* Card.module.css */
402
+ .grid {
403
+ display: grid;
404
+ grid-template-columns: 1fr;
405
+ }
406
+
407
+ :global(body.fds-breakpoint-lg-min) .grid {
408
+ grid-template-columns: repeat(3, 1fr);
409
+ }
410
+ ```
411
+
412
+ For JS conditional logic, use the media query hooks:
413
+
414
+ ```tsx
415
+ import {
416
+ Breakpoint,
417
+ useMaxWidthMediaQuery,
418
+ useMinWidthMediaQuery,
419
+ useMediaQuery,
420
+ useResolvedBreakpointToken,
421
+ parsePxOrRemValue,
422
+ } from "@lifesg/react-design-system/theme";
423
+
424
+ const ExampleComponent = () => {
425
+ // Simple max/min width checks
426
+ const isMobile = useMaxWidthMediaQuery("sm");
427
+ const isDesktop = useMinWidthMediaQuery("lg");
428
+
429
+ // Custom query
430
+ const isTablet = useMediaQuery({ maxWidth: Breakpoint["lg-max"] });
431
+
432
+ // When the actual pixel value is required (e.g. for a third-party library)
433
+ const tablet = parsePxOrRemValue(
434
+ useResolvedBreakpointToken(Breakpoint["lg-max"])
435
+ );
436
+ };
437
+ ```
438
+
439
+ ### Breakpoint tiers
440
+
441
+ | Tier | Screen width | `*-min` value | `*-max` value | Columns | Gutter | Margin |
442
+ | ----- | -------------- | ------------- | ------------- | ------- | ------ | ------ |
443
+ | `xxs` | 0 – 320 px | — | 320 px | 8 | 16 px | 24 px |
444
+ | `xs` | 321 – 375 px | 321 px | 375 px | 8 | 16 px | 24 px |
445
+ | `sm` | 376 – 480 px | 376 px | 480 px | 8 | 16 px | 24 px |
446
+ | `md` | 481 – 768 px | 481 px | 768 px | 8 | 16 px | 24 px |
447
+ | `lg` | 769 – 1200 px | 769 px | 1200 px | 12 | 32 px | 48 px |
448
+ | `xl` | 1201 – 1440 px | 1201 px | 1440 px | 12 | 32 px | 48 px |
449
+ | `xxl` | ≥ 1441 px | 1441 px | — | 12 | 32 px | 48 px |
450
+
451
+ ---
452
+
453
+ ## Shadow Tokens
454
+
455
+ ```tsx
456
+ <div style={{ boxShadow: Shadow["sm-subtle"] }} />
457
+ ```
458
+
459
+ | Token key | Common use |
460
+ | ----------------- | ------------------------------------- |
461
+ | `xs-subtle` | Subtle glow / container resting state |
462
+ | `xs-strong` | Strong glow / hover or active state |
463
+ | `xs-focus-strong` | Keyboard focus ring on inputs |
464
+ | `xs-error-strong` | Error focus ring on inputs |
465
+ | `sm-subtle` | Card / panel resting elevation |
466
+ | `sm-strong` | Card hover / lifted state |
467
+ | `md-subtle` | Modal / drawer resting elevation |
468
+ | `md-strong` | Modal hover or prominent elevation |
469
+
470
+ ---
471
+
472
+ ## Border Tokens
473
+
474
+ `Border` is a flat object of CSS variable strings. Compose border properties manually using the tokens.
475
+
476
+ ```tsx
477
+ import { Border } from "@lifesg/react-design-system/theme";
478
+
479
+ // Individual properties
480
+ <div
481
+ style={{
482
+ borderWidth: Border["width-010"],
483
+ borderStyle: Border.solid,
484
+ borderColor: Colour.border,
485
+ }}
486
+ />
487
+
488
+ // Shorthand using template literal
489
+ <div style={{ border: `${Border["width-010"]} ${Border.solid} ${Colour.border}` }} />
490
+ ```
491
+
492
+ For dashed border styling, use the `DashedBorder` component instead:
493
+
494
+ ```tsx
495
+ import { DashedBorder } from "@lifesg/react-design-system/dashed-border";
496
+ import { Border, Colour, Radius } from "@lifesg/react-design-system/theme";
497
+
498
+ <DashedBorder
499
+ radius={Radius["sm"]}
500
+ thickness={Border["width-040"]}
501
+ colour={Colour["border"]}
502
+ >
503
+ {/* children */}
504
+ </DashedBorder>;
505
+ ```
506
+
507
+ ### Token reference
508
+
509
+ | Token key | Value | Common use |
510
+ | ----------- | ----- | --------------------------- |
511
+ | `width-005` | 0.5px | Hairline divider |
512
+ | `width-010` | 1px | Default input / card border |
513
+ | `width-020` | 2px | Emphasis / selected border |
514
+ | `width-040` | 4px | Strong emphasis border |
515
+ | `solid` | solid | Border style |
516
+
517
+ ---
518
+
519
+ ## Motion Tokens
520
+
521
+ ```tsx
522
+ <button
523
+ style={{
524
+ transition: `background ${Motion["duration-250"]} ${Motion["ease-default"]}`,
525
+ }}
526
+ />
527
+ ```
528
+
529
+ For elements with dynamic height or width (e.g. expandable panels), use `react-spring` instead — CSS transitions cannot handle layout animation with dynamic content.
530
+
531
+ ### Duration
532
+
533
+ | Token key | Value | Use for |
534
+ | --------------- | ------ | ----------------------- |
535
+ | `duration-150` | 150ms | Checkboxes, toggles |
536
+ | `duration-250` | 250ms | Buttons, fades |
537
+ | `duration-350` | 350ms | Icons, short movements |
538
+ | `duration-500` | 500ms | Search bars, toasts |
539
+ | `duration-800` | 800ms | Notifications, cards |
540
+ | `duration-1000` | 1000ms | Full-screen transitions |
541
+
542
+ ### Easing
543
+
544
+ | Token key | Value | Use for |
545
+ | --------------- | -------------------------------------- | ------------------------------------- |
546
+ | `ease-default` | `cubic-bezier(0.45, 0.05, 0.55, 0.95)` | Non-moving elements — opacity, colour |
547
+ | `ease-standard` | `cubic-bezier(0.86, 0, 0.07, 1)` | Visible throughout motion — dropdowns |