@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.
- package/cjs/external/dompurify/dist/purify.es.js +4 -2
- package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
- package/cjs/modal-v2/slots/slot-styles.js +1 -1
- package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
- package/cjs/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
- package/cjs/notification-banner/notification-banner-hoc.js +1 -1
- package/cjs/notification-banner/notification-banner-hoc.js.map +1 -1
- package/external/dompurify/dist/purify.es.js +4 -2
- package/external/dompurify/dist/purify.es.js.map +1 -1
- package/modal-v2/slots/slot-styles.js +1 -1
- package/modal-v2/slots/slot-styles.js.map +1 -1
- package/modal-v2/slots/{slot-styles_ayb9il.css → slot-styles_ygu6zc.css} +1 -1
- package/notification-banner/notification-banner-hoc.js +1 -1
- package/notification-banner/notification-banner-hoc.js.map +1 -1
- package/package.json +2 -2
- package/scripts/cli.js +18 -0
- package/skills/fds-build/SKILL.md +167 -0
- package/skills/fds-build/catalogue.md +181 -0
- package/skills/fds-build/evals/evals.json +519 -0
- package/skills/fds-build/resources/components/accordion.md +128 -0
- package/skills/fds-build/resources/components/alert.md +54 -0
- package/skills/fds-build/resources/components/animation.md +18 -0
- package/skills/fds-build/resources/components/avatar.md +10 -0
- package/skills/fds-build/resources/components/badge.md +18 -0
- package/skills/fds-build/resources/components/box-container.md +61 -0
- package/skills/fds-build/resources/components/breadcrumb.md +60 -0
- package/skills/fds-build/resources/components/button.md +74 -0
- package/skills/fds-build/resources/components/calendar.md +113 -0
- package/skills/fds-build/resources/components/card.md +43 -0
- package/skills/fds-build/resources/components/checkbox.md +53 -0
- package/skills/fds-build/resources/components/countdown-timer.md +20 -0
- package/skills/fds-build/resources/components/data-table.md +142 -0
- package/skills/fds-build/resources/components/date-navigator.md +25 -0
- package/skills/fds-build/resources/components/divider.md +23 -0
- package/skills/fds-build/resources/components/drawer.md +78 -0
- package/skills/fds-build/resources/components/error-display.md +137 -0
- package/skills/fds-build/resources/components/feedback-rating.md +61 -0
- package/skills/fds-build/resources/components/file-download.md +54 -0
- package/skills/fds-build/resources/components/file-upload.md +105 -0
- package/skills/fds-build/resources/components/filter.md +228 -0
- package/skills/fds-build/resources/components/footer.md +94 -0
- package/skills/fds-build/resources/components/form-date-input.md +85 -0
- package/skills/fds-build/resources/components/form-date-range-input.md +146 -0
- package/skills/fds-build/resources/components/form-e-signature.md +55 -0
- package/skills/fds-build/resources/components/form-input.md +65 -0
- package/skills/fds-build/resources/components/form-masked-input.md +69 -0
- package/skills/fds-build/resources/components/form-multi-select.md +100 -0
- package/skills/fds-build/resources/components/form-nested-multi-select.md +159 -0
- package/skills/fds-build/resources/components/form-nested-select.md +151 -0
- package/skills/fds-build/resources/components/form-otp-verification.md +175 -0
- package/skills/fds-build/resources/components/form-phone-number-input.md +87 -0
- package/skills/fds-build/resources/components/form-predictive-text-input.md +108 -0
- package/skills/fds-build/resources/components/form-select.md +103 -0
- package/skills/fds-build/resources/components/form-slider.md +116 -0
- package/skills/fds-build/resources/components/form-textarea.md +116 -0
- package/skills/fds-build/resources/components/form-time-range-picker.md +98 -0
- package/skills/fds-build/resources/components/form-timepicker.md +77 -0
- package/skills/fds-build/resources/components/form-unit-number-input.md +138 -0
- package/skills/fds-build/resources/components/fullscreen-image-carousel.md +159 -0
- package/skills/fds-build/resources/components/histogram-slider.md +41 -0
- package/skills/fds-build/resources/components/icon.md +167 -0
- package/skills/fds-build/resources/components/image-button.md +12 -0
- package/skills/fds-build/resources/components/input.md +51 -0
- package/skills/fds-build/resources/components/language-switcher.md +15 -0
- package/skills/fds-build/resources/components/layout.md +156 -0
- package/skills/fds-build/resources/components/link-list.md +120 -0
- package/skills/fds-build/resources/components/local-nav.md +119 -0
- package/skills/fds-build/resources/components/masthead.md +14 -0
- package/skills/fds-build/resources/components/menu.md +109 -0
- package/skills/fds-build/resources/components/modal.md +150 -0
- package/skills/fds-build/resources/components/navbar.md +270 -0
- package/skills/fds-build/resources/components/notification-banner.md +106 -0
- package/skills/fds-build/resources/components/pagination.md +104 -0
- package/skills/fds-build/resources/components/pill.md +55 -0
- package/skills/fds-build/resources/components/popover.md +80 -0
- package/skills/fds-build/resources/components/progress-indicator.md +47 -0
- package/skills/fds-build/resources/components/radio-button.md +66 -0
- package/skills/fds-build/resources/components/schedule.md +78 -0
- package/skills/fds-build/resources/components/sidenav.md +183 -0
- package/skills/fds-build/resources/components/singpass-button.md +14 -0
- package/skills/fds-build/resources/components/smart-app-banner.md +28 -0
- package/skills/fds-build/resources/components/tab.md +107 -0
- package/skills/fds-build/resources/components/table.md +65 -0
- package/skills/fds-build/resources/components/tag.md +68 -0
- package/skills/fds-build/resources/components/text-list.md +44 -0
- package/skills/fds-build/resources/components/time-slot-bar-week.md +66 -0
- package/skills/fds-build/resources/components/time-slot-bar.md +57 -0
- package/skills/fds-build/resources/components/time-slot-week-view.md +70 -0
- package/skills/fds-build/resources/components/timeline.md +69 -0
- package/skills/fds-build/resources/components/timetable.md +123 -0
- package/skills/fds-build/resources/components/toast.md +72 -0
- package/skills/fds-build/resources/components/toggle.md +119 -0
- package/skills/fds-build/resources/components/typography.md +86 -0
- package/skills/fds-build/resources/components/uneditable-section.md +84 -0
- package/skills/fds-build/resources/foundations/layout-composition-patterns.md +53 -0
- package/skills/fds-build/resources/foundations/tokens.md +547 -0
- package/skills/fds-build/resources/patterns/states.md +300 -0
- package/skills/fds-build/resources/setup/setup.md +202 -0
- package/skills/fds-build/resources/templates/form.md +427 -0
- 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 |
|