@guardian/stand 0.0.12 → 0.0.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/README.md CHANGED
@@ -1,1804 +1,158 @@
1
- # Stand - a tools component library
1
+ # `@guardian/stand`
2
2
 
3
3
  _Find what you need on the (news)stand!_
4
4
 
5
- Stand is component library and design system for internal Guardian tools. Any tool should be able to make use of the components as an npm package - `@guardian/stand` - and developers should feel comfortable contributing.
5
+ A tools component library and design system
6
6
 
7
- ## Installation
7
+ Any tool should be able to make use of the components as an npm package, [`@guardian/stand`](https://www.npmjs.com/package/@guardian/stand), and developers should feel comfortable [contributing](https://guardian.github.io/stand/?path=/docs/contributing--docs).
8
8
 
9
- ```bash
10
- $ pnpm add @guardian/stand # or yarn or npm
11
- ```
12
-
13
- You'll also need to have TypeScript, React, and Emotion installed in your project.
14
-
15
- The compatible versions are listed in the `peerDependencies` section of `package.json`.
16
-
17
- Some components have additional dependencies that you will need to install too. See the [Components](#components) section for more details for which components have which peer dependencies.
18
-
19
- ## Foundations
20
-
21
- The Editorial Design System foundations are available via Stand. These are split into two categories: Semantic and Base / Primitives.
22
-
23
- In most cases consumers should use the Semantic tokens, which are more meaningful abstractions of the Base / Primitives tokens, i.e applied to specific use cases.
24
-
25
- The base / primitives tokens are available for low-level use cases, or very specific cases, but these should be avoided where possible in favour of the semantic tokens.
26
-
27
- Stand provides the foundations via CSS variables, as well as JS/TS exports for use in code, which could also be used in CSS-in-JS solutions.
28
-
29
- Base / Primitive tokens should not be overridden if they are used directly, as this could have unintended consequences. Instead override the Semantic tokens which are designed to be overridden.
30
-
31
- ### Fonts
32
-
33
- Most applications should only need to load the `Open Sans` and `Guardian Headline` fonts, as these are the primary fonts used across the Guardian's Editorial Tool design system.
34
-
35
- You only need to load Guardian Text Egyptian if you're planning to use it in your project, in most cases you only need this when working on Guardian editorial content on an editorial tool, i.e. when using `article-body-*` semantic tokens.
36
-
37
- ##### Open Sans
38
-
39
- ##### Guardian Hosted (Recommended)
40
-
41
- We are self hosting this font under a Guardian specific domain, so you can load the Open Sans variable font CSS file from our CDN:
42
-
43
- Via CSS `@import`:
44
-
45
- ```css
46
- @import url('https://assets.guim.co.uk/fonts/open-sans/OpenSans.css');
47
- ```
48
-
49
- Via HTML `<link>`:
50
-
51
- ```html
52
- <link
53
- rel="stylesheet"
54
- href="https://assets.guim.co.uk/fonts/open-sans/OpenSans.css"
55
- />
56
- ```
57
-
58
- We also publish the same `OpenSans.css` file in the `@guardian/stand` package if you prefer to import it from there and you're using a bundler that supports CSS imports:
59
-
60
- ```ts
61
- import '@guardian/stand/fonts/OpenSans.css';
62
- ```
63
-
64
- All options use the font files hosted on the Guardian CDN at `https://assets.guim.co.uk/fonts/open-sans/woff2/`.
65
-
66
- You can also create your own CSS `@font-face` declarations, using the same format as in the `OpenSans.css` file, and/or self host the font files yourself if needed.
67
-
68
- ##### via Google Fonts
69
-
70
- The Open Sans variable font can also be loaded via Google Fonts, but we recommend using the Guardian hosted version to avoid loading from a third party domain:
71
-
72
- 1. Visit [Google Fonts - Open Sans](https://fonts.google.com/specimen/Open+Sans)
73
- 2. Click "Get Font" -> "Get embed code"
74
- 3. Click "Change styles" dropdown
75
- 4. Use "Full axis" for all options (Italic, Weight, Width)
76
- 5. Copy the relevant `<link>` tag or `@import` code snippet into your project
77
- - You don't need to include the CSS class, as the design system will handle applying the correct font-family via CSS variables or JS/TS tokens.
78
-
79
- #### Guardian Fonts
80
-
81
- Make sure to visit [guardian/fonts](https://github.com/guardian/fonts) repo for the latest information on how to self-host these fonts.
82
-
83
- In general, we always want to use the `full-not-hinted` versions of the fonts where possible.
84
-
85
- ##### Guardian Headline
86
-
87
- We only use the bold weight (700) of Guardian Headline in the design system.
88
-
89
- ```css
90
- @font-face {
91
- font-family: 'GH Guardian Headline';
92
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-headline/full-not-hinted/GHGuardianHeadline-Bold.woff2')
93
- format('woff2');
94
- font-weight: 700;
95
- font-style: normal;
96
- font-display: swap;
97
- }
98
- ```
99
-
100
- ##### Guardian Text Egyptian
101
-
102
- We want the regular/normal weight (400), the bold weight (700), and the italic version of each weight for Guardian Text Egyptian in the design system.
103
-
104
- ```css
105
- @font-face {
106
- font-family: 'GuardianTextEgyptian';
107
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-Regular.woff2')
108
- format('woff2');
109
- font-weight: 400;
110
- font-style: normal;
111
- font-display: swap;
112
- }
113
- @font-face {
114
- font-family: 'GuardianTextEgyptian';
115
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-RegularItalic.woff2')
116
- format('woff2');
117
- font-weight: 400;
118
- font-style: italic;
119
- font-display: swap;
120
- }
121
- @font-face {
122
- font-family: 'GuardianTextEgyptian';
123
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-Bold.woff2')
124
- format('woff2');
125
- font-weight: 700;
126
- font-style: normal;
127
- font-display: swap;
128
- }
129
- @font-face {
130
- font-family: 'GuardianTextEgyptian';
131
- src: url('https://assets.guim.co.uk/static/frontend/fonts/guardian-textegyptian/full-not-hinted/GuardianTextEgyptian-BoldItalic.woff2')
132
- format('woff2');
133
- font-weight: 700;
134
- font-style: italic;
135
- font-display: swap;
136
- }
137
- ```
138
-
139
- ### Semantic
140
-
141
- #### Colors
142
-
143
- _Status: WIP_
144
-
145
- ```ts
146
- import { css } from '@emotion/react';
147
- import { semanticColors } from '@guardian/stand'; // JS/TS usage
148
- import '@guardian/stand/semantic/colors.css'; // CSS usage
149
-
150
- const stringStyle = css`
151
- color: ${semanticColors.text.default}; /* JS/TS usage */
152
- background-color: var(--semantic-colors-bg-default-on-light); /* CSS usage */
153
- `;
154
-
155
- const objectStyle = {
156
- color: semanticColors.text.default /* JS/TS usage */,
157
- backgroundColor: 'var(--semantic-colors-bg-default-on-light)' /* CSS usage */,
158
- };
159
- ```
160
-
161
- For a list of available semantic color styles see the [Storybook Semantic Colors](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-semantic-color-palette--docs) section.
162
-
163
- For a full list of CSS Semantic Color tokens see [`semantic/colors.css`](./src/styleD/build/css/semantic/colors.css).
9
+ This library is split into two main sections:
164
10
 
165
- #### Typography
11
+ - [Tools Design System](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs)
12
+ - The core design system and components used across the Guardian's internal tools.
13
+ - e.g. Colour, Typography, Buttons, Forms, etc.
14
+ - [Editorial Components](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs)
15
+ - A collection of highly specific components shared across a smaller number of tools.
16
+ - e.g. Byline editor, Tag picker, etc.
166
17
 
167
- _Status: WIP_
18
+ Currently maintained by the P&E DevX E2E team, but open to contributions from anyone in the company. Do reach out for help or to get involved!
168
19
 
169
- ```ts
170
- import { css } from '@emotion/react';
171
- import { semanticColors, semanticTypography } from '@guardian/stand'; // JS/TS usage
172
- import {
173
- convertTypographyToEmotionObjectStyle, // helper function to convert from typography token object to emotion CSS object style
174
- convertTypographyToEmotionStringStyle, // helper function to convert from typography token object to emotion CSS string style
175
- } from '@guardian/stand/utils'; // Utils for working with design tokens
176
- import '@guardian/stand/semantic/typography.css'; // CSS usage
177
-
178
- /* JS/TS usage */
179
- const stringStyleJS = css`
180
- // other styles e.g.
181
- color: ${semanticColors.text.default};
182
-
183
- /* (recommended) emotion string style usage helper function*/
184
- ${convertTypographyToEmotionStringStyle(
185
- semanticTypography['body-compact-md'],
186
- )}
187
-
188
- /* or direct usage without helper function */
189
- font: ${semanticTypography['body-compact-md'].font};
190
- letter-spacing: ${semanticTypography['body-compact-md'].letterSpacing};
191
- font-variation-settings: 'wdth'
192
- ${semanticTypography['body-compact-md'].fontWidth};
193
- `;
194
- const objectStyleJS = {
195
- // other styles e.g.
196
- color: semanticColors.text.default,
197
-
198
- // (recommended) emotion object style usage helper function
199
- ...convertTypographyToEmotionObjectStyle(
200
- semanticTypography['body-compact-sm'],
201
- ),
202
-
203
- // or direct usage without helper function
204
- font: semanticTypography['body-compact-sm'].font,
205
- letterSpacing: semanticTypography['body-compact-sm'].letterSpacing,
206
- fontVariationSettings: `'wdth' ${semanticTypography['body-compact-sm'].fontWidth}`,
207
- };
208
-
209
- /* CSS usage */
210
- const stringStyleCSS = css`
211
- /* CSS usage */
212
- font: var(--semantic-typography-body-compact-sm-font);
213
- letter-spacing: var(--semantic-typography-body-compact-sm-letter-spacing);
214
- font-variation-settings: 'wdth'
215
- var(--semantic-typography-body-compact-sm-font-width);
216
- `;
217
- const objectStyleCSS = {
218
- font: 'var(--semantic-typography-body-compact-sm-font)',
219
- letterSpacing: 'var(--semantic-typography-body-compact-sm-letter-spacing)',
220
- fontVariationSettings: `'wdth' var(--semantic-typography-body-compact-sm-font-width)`,
221
- };
222
- ```
223
-
224
- For a list of available typography styles see the [Storybook Semantic Typography](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-semantic-typography--docs) section.
225
-
226
- For a full list of CSS Semantic Typography tokens see [`semantic/typography.css`](./src/styleD/build/css/semantic/typography.css).
227
-
228
- #### Sizing
229
-
230
- _Status: WIP_
231
-
232
- ```ts
233
- import { css } from '@emotion/react';
234
- import { semanticSizing } from '@guardian/stand'; // JS/TS usage
235
- import '@guardian/stand/semantic/sizing.css'; // CSS usage
236
-
237
- const stringStyle = css`
238
- height: ${semanticSizing.height.md}; /* JS/TS usage */
239
- border-width: var(--semantic-sizing-border-md); /* CSS usage */
240
- `;
241
-
242
- const objectStyle = {
243
- height: semanticSizing.height.md /* JS/TS usage */,
244
- borderWidth: 'var(--semantic-sizing-border-md)' /* CSS usage */,
245
- };
246
-
247
- // Icon sizing example
248
- const iconStyle = css`
249
- width: ${semanticSizing.icon.lg};
250
- height: ${semanticSizing.icon.lg};
251
- `;
252
- ```
253
-
254
- For a list of available semantic sizing styles see the [Storybook Semantic Sizing](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-semantic-sizing--docs) section.
255
-
256
- For a full list of CSS Semantic Sizing tokens see [`semantic/sizing.css`](./src/styleD/build/css/semantic/sizing.css).
257
-
258
- ### Base / Primitives
259
-
260
- #### Colors
261
-
262
- _Status: WIP_
263
-
264
- ```ts
265
- import { css } from '@emotion/react';
266
- import { baseColors } from '@guardian/stand'; // JS/TS usage
267
- import '@guardian/stand/base/colors.css'; // CSS usage
268
-
269
- const stringStyle = css`
270
- color: ${baseColors.neutral['900']}; /* JS/TS usage */
271
- background-color: var(--base-colors-blue-500); /* CSS usage */
272
- `;
273
-
274
- const objectStyle = {
275
- color: baseColors.neutral['900'] /* JS/TS usage */,
276
- backgroundColor: 'var(--base-colors-blue-500)' /* CSS usage */,
277
- };
278
- ```
279
-
280
- For a list of the available base/primitives color styles see the [Storybook Base Colors](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-color-palette--docs) section.
281
-
282
- For a full list of CSS Base/Primitives Color tokens see [`base/colors.css`](./src/styleD/build/css/base/colors.css).
283
-
284
- #### Typography
285
-
286
- _Status: WIP_
287
-
288
- ```ts
289
- import { css } from '@emotion/react';
290
- import { baseTypography } from '@guardian/stand'; // JS/TS usage
291
- import '@guardian/stand/base/typography.css'; // CSS usage
292
-
293
- /* JS/TS usage */
294
- const stringStyleJS = css`
295
- font-family: ${baseTypography.family['Open Sans']};
296
- font-size: ${baseTypography.size['14-rem']};
297
- font-weight: ${baseTypography.weight['Open Sans'].normal};
298
- font-variation-settings: 'wdth' ${baseTypography.width['Open Sans']};
299
- style: ${baseTypography.style.normal};
300
- line-height: ${baseTypography.lineHeight.normal};
301
- letter-spacing: ${baseTypography.letterSpacing['default-rem']};
302
- `;
303
- const objectStyleJS = {
304
- fontFamily: baseTypography.family['Open Sans'],
305
- fontSize: baseTypography.size['14-rem'],
306
- fontWeight: baseTypography.weight['Open Sans'].normal,
307
- fontVariationSettings: `'wdth' ${baseTypography.width['Open Sans']}`,
308
- fontStyle: baseTypography.style.normal,
309
- lineHeight: baseTypography.lineHeight.normal,
310
- letterSpacing: baseTypography.letterSpacing['default-rem'],
311
- };
312
-
313
- /* CSS usage */
314
- const stringStyleCSS = css`
315
- font-family: var(--base-typography-family-open-sans);
316
- font-size: var(--base-typography-size-14-rem);
317
- font-weight: var(--base-typography-weight-open-sans-normal);
318
- font-variation-settings: 'wdth' var(--base-typography-width-open-sans);
319
- font-style: var(--base-typography-style-normal);
320
- line-height: var(--base-typography-line-height-normal);
321
- letter-spacing: var(--base-typography-letter-spacing-default-rem);
322
- `;
323
- const objectStyleCSS = {
324
- fontFamily: 'var(--base-typography-family-open-sans)',
325
- fontSize: 'var(--base-typography-size-14-rem)',
326
- fontWeight: 'var(--base-typography-weight-open-sans-normal)',
327
- fontVariationSettings: `'wdth' var(--base-typography-width-open-sans)`,
328
- fontStyle: 'var(--base-typography-style-normal)',
329
- lineHeight: 'var(--base-typography-line-height-normal)',
330
- letterSpacing: 'var(--base-typography-letter-spacing-default-rem)',
331
- };
332
- ```
333
-
334
- For a list of the available base/primitives typography tokens see the [Storybook Base Typography](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-typography--docs) section.
335
-
336
- For a full list of CSS Base/Primitives Typography tokens see [`base/typography.css`](./src/styleD/build/css/base/typography.css).
337
-
338
- #### Spacing
339
-
340
- _Status: WIP_
341
-
342
- The `rem` tokens should be used in most cases to ensure scalability across different root font sizes.
343
- `px` tokens are available for cases where a fixed pixel value is required.
344
-
345
- ```ts
346
- import { css } from '@emotion/react';
347
- import { baseSpacing } from '@guardian/stand'; // JS/TS usage
348
- import '@guardian/stand/base/spacing.css'; // CSS usage
349
-
350
- /* JS/TS usage */
351
- const stringStyleJS = css`
352
- padding: ${baseSpacing['16-rem']};
353
- margin-bottom: ${baseSpacing['24-rem']};
354
- gap: ${baseSpacing['8-rem']};
355
- `;
356
- const objectStyleJS = {
357
- padding: baseSpacing['16-rem'],
358
- marginBottom: baseSpacing['24-rem'],
359
- gap: baseSpacing['8-rem'],
360
- };
361
-
362
- /* CSS usage */
363
- const stringStyleCSS = css`
364
- padding: var(--base-spacing-16-rem);
365
- margin-bottom: var(--base-spacing-24-rem);
366
- gap: var(--base-spacing-8-rem);
367
- `;
368
- const objectStyleCSS = {
369
- padding: 'var(--base-spacing-16-rem)',
370
- marginBottom: 'var(--base-spacing-24-rem)',
371
- gap: 'var(--base-spacing-8-rem)',
372
- };
373
- ```
374
-
375
- For a list of the available base/primitives spacing tokens see the [Storybook Base Spacing](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-spacing--docs) section.
376
-
377
- For a full list of CSS Base/Primitives Spacing tokens see [`base/spacing.css`](./src/styleD/build/css/base/spacing.css).
378
-
379
- #### Sizing
380
-
381
- _Status: WIP_
382
-
383
- The `rem` tokens should be used in most cases to ensure scalability across different root font sizes.
384
- `px` tokens are available for cases where a fixed pixel value is required.
385
-
386
- ```ts
387
- import { css } from '@emotion/react';
388
- import { baseSizing } from '@guardian/stand'; // JS/TS usage
389
- import '@guardian/stand/base/sizing.css'; // CSS usage
390
-
391
- /* JS/TS usage */
392
- const stringStyleJS = css`
393
- width: ${baseSizing['size-48-rem']};
394
- height: ${baseSizing['size-24-rem']};
395
- min-width: ${baseSizing['size-24-rem']};
396
- `;
397
- const objectStyleJS = {
398
- width: baseSizing['size-48-rem'],
399
- height: baseSizing['size-24-rem'],
400
- minWidth: baseSizing['size-24-rem'],
401
- };
402
-
403
- /* CSS usage */
404
- const stringStyleCSS = css`
405
- width: var(--base-sizing-size-48-rem);
406
- height: var(--base-sizing-size-24-rem);
407
- min-width: var(--base-sizing-size-24-rem);
408
- `;
409
- const objectStyleCSS = {
410
- width: 'var(--base-sizing-size-48-rem)',
411
- height: 'var(--base-sizing-size-24-rem)',
412
- minWidth: 'var(--base-sizing-size-24-rem)',
413
- };
414
- ```
20
+ Stand is built with [React](https://reactjs.org/), [Emotion](https://emotion.sh/docs/introduction), and [TypeScript](https://www.typescriptlang.org/), but the design system is designed to be as framework-agnostic as possible in that styles can be used without these dependencies if needed.
415
21
 
416
- For a list of the available base/primitives sizing tokens see the [Storybook Base Sizing](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-sizing--docs) section.
22
+ ## Contents
417
23
 
418
- For a full list of CSS Base/Primitives Sizing tokens see [`base/sizing.css`](./src/styleD/build/css/base/sizing.css).
24
+ - [Documentation](#documentation)
25
+ - [Install](#install)
26
+ - [Usage](#usage)
27
+ - [Contributing](#contributing)
28
+ - [Tasks](#tasks)
29
+ - [Design Tokens and Style Dictionary](#design-tokens-and-style-dictionary)
30
+ - [Compatibility](#compatibility)
31
+ - [Documentation Site Map](#documentation-site-map)
419
32
 
420
- #### Radius
421
-
422
- _Status: WIP_
423
-
424
- The `rem` tokens should be used in most cases to ensure scalability across different root font sizes.
425
- `px` tokens are available for cases where a fixed pixel value is required.
426
-
427
- ```ts
428
- import { css } from '@emotion/react';
429
- import { baseRadius } from '@guardian/stand'; // JS/TS usage
430
- import '@guardian/stand/base/radius.css'; // CSS usage
431
-
432
- /* JS/TS usage */
433
- const stringStyleJS = css`
434
- border-radius: ${baseRadius['corner-4-rem']};
435
- `;
436
- const objectStyleJS = {
437
- borderRadius: baseRadius['corner-4-rem'],
438
- };
439
-
440
- /* CSS usage */
441
- const stringStyleCSS = css`
442
- border-radius: var(--base-radius-corner-4-rem);
443
- `;
444
- const objectStyleCSS = {
445
- borderRadius: 'var(--base-radius-corner-4-rem)',
446
- };
447
- ```
448
-
449
- For a list of the available base/primitives radius tokens see the [Storybook Base Radius](https://68c12e3ed577cb56abfd31bf-kggeezequd.chromatic.com/?path=/docs/stand-editorial-design-system-base-radius--docs) section.
450
-
451
- For a full list of CSS Base/Primitives Radius tokens see [`base/radius.css`](./src/styleD/build/css/base/radius.css).
452
-
453
- ## Components - Base
454
-
455
- General purpose components for use across a variety of editorial tools based on the design system.
456
-
457
- ### `Avatar`
458
-
459
- The Avatar component displays a user's profile image or initials in a circular container. It supports multiple sizes, colors, and automatic fallback handling.
460
-
461
- **When to use**
462
-
463
- - Display user profiles in lists, comments, or headers
464
- - Show user identity in messaging interfaces
465
- - Represent users in collaborative features
466
-
467
- **Peer dependencies:**
468
-
469
- - `@emotion/react`
470
- - `react`
471
- - `react-dom`
472
- - `typescript`
473
-
474
- See the `peerDependencies` section of `package.json` for compatible versions.
475
-
476
- See [avatar custom component build](#avatar-custom-component-build) for usage without React/Emotion.
477
-
478
- #### Example usage
479
-
480
- See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/guardian-stand-avatar-component-mqvzh5)
481
-
482
- ```tsx
483
- import { Avatar } from '@guardian/stand/avatar';
484
-
485
- /* types, if required */
486
- import type { AvatarProps, AvatarTheme } from '@guardian/stand/avatar';
487
-
488
- /* Initials only */
489
- <Avatar initials="AB" size="md" />
490
-
491
- /* Image with alt text */
492
- <Avatar
493
- src="https://example.com/avatar.jpg"
494
- alt="User Name"
495
- size="md"
496
- />
497
-
498
- /* Image with fallback initials and a specific color */
499
- <Avatar
500
- src="https://example.com/avatar.jpg"
501
- alt="User Name"
502
- initials="AB"
503
- color="blue"
504
- size="md"
505
- />
506
- ```
507
-
508
- #### Props
509
-
510
- | Name | Type | Required | Default | Description |
511
- | -------------- | ------------------ | ----------- | ------------------------------- | ---------------------------------------------- |
512
- | `size` | `'sm'` \| `'md'` | No | `'md'` | Size of the avatar. |
513
- | `color` | Various | No | Deterministic based on initials | Color of the avatar. |
514
- | `initials` | `string` | Conditional | N/A | Initials to display when no image is provided. |
515
- | `src` | `string` | Conditional | N/A | URL of the avatar image. |
516
- | `alt` | `string` | Conditional | N/A | Alt text for the image for accessibility. |
517
- | `theme` | `AvatarTheme` | No | N/A | Custom theme overrides for the avatar. |
518
- | `cssOverrides` | `SerializedStyles` | No | N/A | Custom CSS styles for the avatar. |
519
- | `className` | `string` | No | N/A | Additional class name(s) for the avatar. |
520
-
521
- When using `src`, the `alt` prop is required for accessibility.
522
- When not using `src`, the `initials` prop is required.
523
-
524
- **`size`**
525
-
526
- The avatar supports two sizes:
527
-
528
- - `sm` (small): 32px
529
- - `md` (medium): 40px
530
-
531
- **`color`**
532
-
533
- When no `color` is set, the avatar picks a deterministic color based on the initials (with the exception of `outlined`). You can also specify a color explicitly.
534
-
535
- Available colors: `outlined`, `blue`, `green`, `red`, `cyan`, `teal`, `cool-purple`, `warm-purple`, `magenta`, `orange`, `yellow`
536
-
537
- #### Customisation
538
-
539
- We recommend using the Avatar component as provided, but it can be customised using the `theme` or `cssOverrides` props as required.
540
-
541
- **Custom theme**
542
-
543
- The `theme` prop allows you to override specific design tokens for the Avatar component:
544
-
545
- ```tsx
546
- import type { AvatarTheme } from '@guardian/stand/avatar';
547
- import { Avatar } from '@guardian/stand/avatar';
548
- import { baseColors, baseSizing } from '@guardian/stand';
549
-
550
- const customTheme: AvatarTheme = {
551
- shared: {
552
- color: {
553
- blue: {
554
- background: baseColors.blue[100],
555
- text: baseColors.neutral[900],
556
- },
557
- },
558
- },
559
- md: {
560
- size: baseSizing['size-48-rem'],
561
- },
562
- };
563
-
564
- const Component = () => (
565
- <Avatar color="blue" initials="CT" size="md" theme={customTheme} />
566
- );
567
- ```
568
-
569
- **CSS overrides**
570
-
571
- The `cssOverrides` prop allows you to pass custom CSS to the Avatar component, if the theme prop is not sufficient:
572
-
573
- ```tsx
574
- import { Avatar } from '@guardian/stand/avatar';
575
- import { baseColors, baseSizing, baseSpacing } from '@guardian/stand';
576
- import { css } from '@emotion/react';
577
-
578
- const customStyles = css`
579
- border: ${baseSizing['size-2-rem']} solid ${baseColors.red[500]};
580
- margin: ${baseSpacing['8-rem']};
581
- `;
582
-
583
- const Component = () => (
584
- <Avatar initials="CO" size="md" color="red" cssOverrides={customStyles} />
585
- );
586
- ```
587
-
588
- #### Avatar Custom Component Build
589
-
590
- If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom Avatar component using the styles defined in the `AvatarTheme` type.
591
-
592
- You will however be responsible for any additional functionality on top of the styles, for example image loading, image fallback, accessibility etc.
593
-
594
- See "Custom Component" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/guardian-stand-avatar-component-mqvzh5)
595
-
596
- **`css`**
597
-
598
- You can import the Avatar styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
599
-
600
- ```css
601
- /* import the font and avatar styles */
602
- @import '@guardian/stand/fonts/OpenSans.css';
603
- @import '@guardian/stand/component/avatar.css';
604
-
605
- /*
606
- or for scenarios where you have to use relative paths/node_modules directly:
607
-
608
- @import 'node_modules/@guardian/stand/dist/fonts/OpenSans.css';
609
- @import 'node_modules/@guardian/stand/dist/styleD/build/css/component/avatar.css';
610
- */
611
-
612
- /* example setup of avatar style using md size and blue color */
613
- .stand-avatar {
614
- display: var(--component-avatar-shared-display);
615
- align-items: var(--component-avatar-shared-align-items);
616
- justify-content: var(--component-avatar-shared-justify-content);
617
- overflow: var(--component-avatar-shared-overflow);
618
- flex-shrink: var(--component-avatar-shared-flex-shrink);
619
- border-radius: var(--component-avatar-shared-border-radius);
620
- background-color: var(--component-avatar-shared-color-blue-background);
621
- width: var(--component-avatar-md-size);
622
- height: var(--component-avatar-md-size);
623
- border: var(--component-avatar-shared-color-blue-border);
624
- color: var(--component-avatar-shared-color-blue-text);
625
- font: var(--component-avatar-md-typography-font);
626
- letter-spacing: var(--component-avatar-md-typography-letter-spacing);
627
- font-variation-settings: 'wdth'
628
- var(--component-avatar-md-typography-font-width);
629
- }
630
-
631
- /* example setup for avatar image */
632
- .stand-avatar > img {
633
- width: 100%;
634
- height: 100%;
635
- object-fit: cover;
636
- }
637
- ```
638
-
639
- ```html
640
- <!-- example usage of avatar style in html with avatar -->
641
- <div class="stand-avatar">AB</div>
642
-
643
- <!-- example usage of avatar style in html with avatar image -->
644
- <div class="stand-avatar">
645
- <img src="https://example.com/avatar.jpg" alt="User Name" />
646
- </div>
647
- ```
648
-
649
- **TypeScript/JavaScript**
650
-
651
- Use the `componentAvatar` variable and the `ComponentAvatar` type to define your custom styles in TypeScript/JavaScript:
652
-
653
- ```ts
654
- import type { ComponentAvatar } from '@guardian/stand'; // if types required
655
- import { componentAvatar } from '@guardian/stand';
656
-
657
- const style = `
658
- display: ${componentAvatar.shared.display};
659
- align-items: ${componentAvatar.shared['align-items']};
660
- justify-content: ${componentAvatar.shared['justify-content']};
661
- overflow: ${componentAvatar.shared.overflow};
662
- flex-shrink: ${componentAvatar.shared['flex-shrink']};
663
- border-radius: ${componentAvatar.shared['border-radius']};
664
- background-color: ${componentAvatar.shared.color.blue.background};
665
- width: ${componentAvatar.md.size};
666
- height: ${componentAvatar.md.size};
667
- border: ${componentAvatar.shared.color.blue.border};
668
- color: ${componentAvatar.shared.color.blue.text};
669
- font: ${componentAvatar.md.typography.font};
670
- letter-spacing: ${componentAvatar.md.typography.letterSpacing};
671
- font-variation-settings: 'wdth' ${componentAvatar.md.typography.fontWidth};
672
- `;
673
-
674
- const imgStyle = `
675
- width: 100%;
676
- height: 100%;
677
- object-fit: cover;
678
- `;
679
-
680
- // e.g. adding to DOM using vanilla JS styles
681
- document.getElementById('app')!.innerHTML = `
682
- <h2>
683
- Using <code>typescript</code>/<code>javascript</code>
684
- </h2>
685
- <div style="${style}">AB</div>
686
- <div style="${style}">
687
- <img
688
- style="${imgStyle}"
689
- src="https://example.com/avatar.jpg"
690
- alt="User Name"
691
- />
692
- </div>
693
- `;
694
- ```
695
-
696
- ### `Button`
697
-
698
- A React Aria powered button that follows Stand design and accessibility defaults. It supports multiple visual variants, four sizes, disabled state handling, and render props for pending interactions.
699
-
700
- **When to use**
701
-
702
- - Primary and secondary actions that require clear affordance
703
- - Form submissions or confirm/cancel flows
704
- - Re-usable button styles that align with Stand tokens
705
-
706
- **Peer dependencies:**
707
-
708
- - `@emotion/react`
709
- - `react`
710
- - `react-dom`
711
- - `react-aria-components`
712
- - `typescript`
713
-
714
- See the `peerDependencies` section of `package.json` for compatible versions.
715
-
716
- See [button custom component build](#button-custom-component-build) for usage without React/Emotion.
717
-
718
- #### Example usage
719
-
720
- See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
721
-
722
- ```tsx
723
- import { Button } from '@guardian/stand/button';
724
- import type { ButtonProps, ButtonTheme } from '@guardian/stand/button';
725
-
726
- const Example = () => (
727
- <>
728
- <Button
729
- variant="emphasised-primary"
730
- size="md"
731
- onPress={() => {
732
- console.log('Primary action');
733
- }}
734
- >
735
- Publish
736
- </Button>
737
-
738
- <Button variant="neutral-secondary" size="sm" isDisabled>
739
- Disabled
740
- </Button>
741
- </>
742
- );
743
- ```
744
-
745
- #### Props
746
-
747
- | Name | Type | Required | Default | Description |
748
- | -------------- | -------------------------------------------------------------------------------------------------- | -------- | ---------------------- | ------------------------------------------------------------------------------------------------- |
749
- | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the button dimensions and typography. |
750
- | `variant` | `'emphasised-primary'` \| `'emphasised-secondary'` \| `'neutral-primary'` \| `'neutral-secondary'` | No | `'emphasised-primary'` | Chooses the colour scheme and interaction states. |
751
- | `children` | `ReactNode` \| `RenderProps` | Yes | N/A | Content inside the button. Render props receive `{ isPending }` from React Aria. |
752
- | `isDisabled` | `boolean` | No | `false` | Disables interaction and applies disabled styling. |
753
- | `theme` | `ButtonTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
754
- | `cssOverrides` | `SerializedStyles` \| `SerializedStyles[]` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
755
- | `className` | `string` | No | N/A | Optional class name forwarded to the root React Aria button. |
756
- | `...props` | `ReactAria Button` props | No | N/A | All other props from `react-aria-components` `Button` (e.g. `type`, `autoFocus`, event handlers). |
757
-
758
- **`size` and `variant`**
759
-
760
- - Sizes: `xs`, `sm`, `md`, `lg`
761
- - Variants: `emphasised-primary`, `emphasised-secondary`, `neutral-primary`, `neutral-secondary`
762
-
763
- #### Customisation
764
-
765
- We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
766
-
767
- **Custom theme**
768
-
769
- ```tsx
770
- import type { ButtonTheme } from '@guardian/stand/button';
771
- import { Button } from '@guardian/stand/button';
772
- import { baseColors } from '@guardian/stand';
773
-
774
- const customTheme: Partial<ButtonTheme> = {
775
- 'emphasised-primary': {
776
- shared: {
777
- backgroundColor: baseColors['cool-purple'][200],
778
- color: baseColors['cool-purple'][900],
779
- border: `2px solid ${baseColors['cool-purple'][700]}`,
780
- ':hover': {
781
- backgroundColor: baseColors['cool-purple'][300],
782
- border: `2px solid ${baseColors['cool-purple'][700]}`,
783
- },
784
- ':active': {
785
- backgroundColor: baseColors['cool-purple'][400],
786
- border: `2px solid ${baseColors['cool-purple'][700]}`,
787
- },
788
- },
789
- },
790
- };
791
-
792
- const Component = () => (
793
- <Button variant="emphasised-primary" size="md" theme={customTheme}>
794
- Custom Themed Button
795
- </Button>
796
- );
797
- ```
798
-
799
- **CSS overrides**
800
-
801
- ```tsx
802
- import { Button } from '@guardian/stand/button';
803
- import { baseSpacing } from '@guardian/stand';
804
- import { css } from '@emotion/react';
805
-
806
- const customStyles = css`
807
- width: 100%;
808
- text-transform: full-width;
809
- font-variant: small-caps;
810
- padding-inline: ${baseSpacing['24-rem']};
811
- `;
812
-
813
- const Component = () => (
814
- <Button variant="neutral-primary" size="sm" cssOverrides={customStyles}>
815
- CSSOverrides Button
816
- </Button>
817
- );
818
- ```
33
+ ## Documentation
819
34
 
820
- #### Button Custom Component Build
35
+ All documentation is available on [Github Pages Storybook](https://guardian.github.io/stand/), which is updated with the latest changes to the library.
821
36
 
822
- If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom `Button`/`LinkButton` component using the styles defined in the `ButtonTheme` type.
37
+ ## Install
823
38
 
824
- You will however be responsible for any additional functionality on top of the styles, for example accessibility, focus management, interaction states etc.
39
+ The library is published to npm:
825
40
 
826
- See "Custom Component" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
827
-
828
- **`css`**
829
-
830
- You can import the Button styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
831
-
832
- ```css
833
- /* import the font and button variables */
834
- @import '@guardian/stand/fonts/OpenSans.css';
835
- @import '@guardian/stand/component/button.css';
836
-
837
- /*
838
- or for scenarios where you have to use relative paths/node_modules directly:
839
-
840
- @import 'node_modules/@guardian/stand/dist/fonts/OpenSans.css';
841
- @import 'node_modules/@guardian/stand/dist/styleD/build/css/component/button.css';
842
- */
843
-
844
- /* shared button styles for all variants */
845
- .stand-button {
846
- display: var(--component-button-shared-display);
847
- -webkit-appearance: var(--component-button-shared-webkit-appearance);
848
- text-align: var(--component-button-shared-text-align);
849
- box-shadow: var(--component-button-shared-box-shadow);
850
- text-decoration: var(--component-button-shared-text-decoration);
851
- cursor: var(--component-button-shared-cursor);
852
- justify-content: var(--component-button-shared-justify-content);
853
- align-items: var(--component-button-shared-align-items);
854
- }
855
- .stand-button:focus-visible {
856
- outline: var(--component-button-shared-focus-visible-outline);
857
- outline-offset: var(--component-button-shared-focus-visible-outline-offset);
858
- }
859
- .stand-button:disabled {
860
- cursor: var(--component-button-shared-disabled-cursor);
861
- }
862
-
863
- /* example setup of button/link button style using md size and emphasised primary variant */
864
- .stand-button-emphasised-primary {
865
- color: var(--component-button-emphasised-primary-shared-color);
866
- background: var(
867
- --component-button-emphasised-primary-shared-background-color
868
- );
869
- height: var(--component-button-emphasised-primary-md-height);
870
- padding: var(--component-button-emphasised-primary-md-padding-top)
871
- var(--component-button-emphasised-primary-md-padding-right)
872
- var(--component-button-emphasised-primary-md-padding-bottom)
873
- var(--component-button-emphasised-primary-md-padding-left);
874
- font: var(--component-button-emphasised-primary-md-typography-font);
875
- letter-spacing: var(
876
- --component-button-emphasised-primary-md-typography-letter-spacing
877
- );
878
- font-variation-settings: 'wdth'
879
- var(--component-button-emphasised-primary-md-typography-font-width);
880
- border: var(--component-button-emphasised-primary-shared-border);
881
- border-radius: var(
882
- --component-button-emphasised-primary-shared-border-radius
883
- );
884
- }
885
- .stand-button-emphasised-primary:hover {
886
- background: var(
887
- --component-button-emphasised-primary-shared-hover-background-color
888
- );
889
- border: var(--component-button-emphasised-primary-shared-hover-border);
890
- }
891
- .stand-button-emphasised-primary:active {
892
- background: var(
893
- --component-button-emphasised-primary-shared-active-background-color
894
- );
895
- border: var(--component-button-emphasised-primary-shared-active-border);
896
- }
897
- .stand-button-emphasised-primary:disabled {
898
- color: var(--component-button-emphasised-primary-shared-disabled-color);
899
- background: var(
900
- --component-button-emphasised-primary-shared-disabled-background-color
901
- );
902
- border: var(--component-button-emphasised-primary-shared-disabled-border);
903
- }
904
-
905
- /* example setup of button/link button style using md size and neutral secondary variant */
906
- .stand-button-neutral-secondary {
907
- color: var(--component-button-neutral-secondary-shared-color);
908
- background: var(--component-button-neutral-secondary-shared-background-color);
909
- height: var(--component-button-neutral-secondary-md-height);
910
- padding: var(--component-button-neutral-secondary-md-padding-top)
911
- var(--component-button-neutral-secondary-md-padding-right)
912
- var(--component-button-neutral-secondary-md-padding-bottom)
913
- var(--component-button-neutral-secondary-md-padding-left);
914
- font: var(--component-button-neutral-secondary-md-typography-font);
915
- letter-spacing: var(
916
- --component-button-neutral-secondary-md-typography-letter-spacing
917
- );
918
- font-variation-settings: 'wdth'
919
- var(--component-button-neutral-secondary-md-typography-font-width);
920
- border: var(--component-button-neutral-secondary-shared-border);
921
- border-radius: var(--component-button-neutral-secondary-shared-border-radius);
922
- }
923
- .stand-button-neutral-secondary:hover {
924
- background: var(
925
- --component-button-neutral-secondary-shared-hover-background-color
926
- );
927
- border: var(--component-button-neutral-secondary-shared-hover-border);
928
- }
929
- .stand-button-neutral-secondary:active {
930
- background: var(
931
- --component-button-neutral-secondary-shared-active-background-color
932
- );
933
- border: var(--component-button-neutral-secondary-shared-active-border);
934
- }
935
- .stand-button-neutral-secondary:disabled {
936
- color: var(--component-button-neutral-secondary-shared-disabled-color);
937
- background: var(
938
- --component-button-neutral-secondary-shared-disabled-background-color
939
- );
940
- border: var(--component-button-neutral-secondary-shared-disabled-border);
941
- }
942
- ```
943
-
944
- ```html
945
- <button class="stand-button stand-button-emphasised-primary">
946
- Button Label
947
- </button>
948
- <button class="stand-button stand-button-emphasised-primary" disabled>
949
- Disabled Button Label
950
- </button>
951
- <a class="stand-button stand-button-neutral-secondary" href="#"
952
- >LinkButton Label</a
953
- >
954
- ```
955
-
956
- **TypeScript/JavaScript**
957
-
958
- Use the `componentButton` variable and the `ComponentButton` type to define your custom styles in TypeScript/JavaScript:
959
-
960
- ```ts
961
- import type { ComponentButton } from '@guardian/stand'; // if types required
962
- import { componentButton } from '@guardian/stand/button';
963
-
964
- /* NB: The HTML style attribute cannot target psuedo selectors, so they haven't been implemented e.g. hover/focus */
965
- const sharedButtonStyles = `
966
- display: ${componentButton.shared.display};
967
- -webkit-appearance: ${componentButton.shared['-webkit-appearance']};
968
- text-align: ${componentButton.shared['text-align']};
969
- box-shadow: ${componentButton.shared['box-shadow']};
970
- text-decoration: ${componentButton.shared['text-decoration']};
971
- cursor: ${componentButton.shared.cursor};
972
- justify-content: ${componentButton.shared['justify-content']};
973
- align-items: ${componentButton.shared['align-items']};
974
- `;
975
-
976
- const emphasisedPrimaryButtonStyles = `
977
- ${sharedButtonStyles}
978
- color: ${componentButton['emphasised-primary'].shared.color};
979
- background: ${componentButton['emphasised-primary'].shared.backgroundColor};
980
- height: ${componentButton['emphasised-primary'].md.height};
981
- padding: ${componentButton['emphasised-primary'].md.padding.top}
982
- ${componentButton['emphasised-primary'].md.padding.right}
983
- ${componentButton['emphasised-primary'].md.padding.bottom}
984
- ${componentButton['emphasised-primary'].md.padding.left};
985
- font: ${componentButton['emphasised-primary'].md.typography.font};
986
- letter-spacing: ${componentButton['emphasised-primary'].md.typography.letterSpacing};
987
- font-variation-settings: 'wdth'
988
- ${componentButton['emphasised-primary'].md.typography.fontWidth};
989
- border: ${componentButton['emphasised-primary'].shared.border};
990
- border-radius: ${componentButton['emphasised-primary'].shared.borderRadius};
991
- `;
992
-
993
- const emphasisedPrimaryButtonDisabledStyles = `
994
- ${emphasisedPrimaryButtonStyles}
995
- cursor: ${componentButton.shared[':disabled'].cursor};
996
- color: ${componentButton['emphasised-primary'].shared[':disabled'].color};
997
- background: ${componentButton['emphasised-primary'].shared[':disabled'].backgroundColor};
998
- border: ${componentButton['emphasised-primary'].shared[':disabled'].border};
999
- `;
1000
-
1001
- const neutralSecondaryButtonStyles = `
1002
- ${sharedButtonStyles}
1003
- color: ${componentButton['neutral-secondary'].shared.color};
1004
- background: ${componentButton['neutral-secondary'].shared.backgroundColor};
1005
- height: ${componentButton['neutral-secondary'].md.height};
1006
- padding: ${componentButton['neutral-secondary'].md.padding.top}
1007
- ${componentButton['neutral-secondary'].md.padding.right}
1008
- ${componentButton['neutral-secondary'].md.padding.bottom}
1009
- ${componentButton['neutral-secondary'].md.padding.left};
1010
- font: ${componentButton['neutral-secondary'].md.typography.font};
1011
- letter-spacing: ${componentButton['neutral-secondary'].md.typography.letterSpacing};
1012
- font-variation-settings: 'wdth'
1013
- ${componentButton['neutral-secondary'].md.typography.fontWidth};
1014
- border: ${componentButton['neutral-secondary'].shared.border};
1015
- border-radius: ${componentButton['neutral-secondary'].shared.borderRadius};
1016
- `;
1017
-
1018
- // e.g. adding to DOM using vanilla JS styles
1019
- document.getElementById('app')!.innerHTML = `
1020
- <button style="${emphasisedPrimaryButtonStyles}">Button Label</button>
1021
- <button disabled style="${emphasisedPrimaryButtonDisabledStyles}">Disabled Button Label</button>
1022
- <a href="#" style="${neutralSecondaryButtonStyles}">LinkButton Label</a>
1023
- `;
1024
- ```
1025
-
1026
- ### `Icon`
1027
-
1028
- The Icon component provides a flexible way to display icons in your application. It supports both Material Symbols (font-based icons) and SVG icons (Material Icons or custom SVG components), with consistent sizing and color control.
1029
-
1030
- **When to use**
1031
-
1032
- - Display icons alongside text or buttons
1033
- - Represent actions, states, or categories visually
1034
- - Provide visual cues in UI elements
1035
-
1036
- **Peer dependencies:**
1037
-
1038
- - `@emotion/react`
1039
- - `react`
1040
- - `react-dom`
1041
- - `typescript`
1042
-
1043
- See the `peerDependencies` section of `package.json` for compatible versions.
1044
-
1045
- See [icon custom component build](#icon-custom-component-build) for usage without React/Emotion.
1046
-
1047
- #### Example usage
1048
-
1049
- See "Emotion/React" heading under the `Icon` component on [codesandbox.io](https://codesandbox.io/p/sandbox/mrzkrw).
1050
-
1051
- ```tsx
1052
- import { Icon } from '@guardian/stand/icon';
1053
- import { baseColors } from '@guardian/stand';
1054
-
1055
- /* types, if required */
1056
- import type { IconProps, IconTheme } from '@guardian/stand/icon';
1057
-
1058
- /* Material Symbols (font icon) */
1059
- <Icon size="md" symbol="home"></Icon>
1060
-
1061
- /* Material Symbols with custom color */
1062
- <Icon size="md" fill={baseColors.red[500]} symbol="home"></Icon>
1063
-
1064
- /* Standalone meaningful icon (use alt prop) */
1065
- <Icon alt="Warning: High priority" symbol="warning"></Icon>
1066
-
1067
- /* Material Icons (SVG) */
1068
- import HomeIcon from '@material-design-icons/svg/outlined/home.svg?react';
1069
- <Icon size="md" alt="Home">
1070
- <HomeIcon />
1071
- </Icon>
1072
-
1073
- /* Custom SVG component */
1074
- const CustomIcon = () => (
1075
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1076
- <path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z" />
1077
- </svg>
1078
- );
1079
-
1080
- <Icon size="lg" fill={baseColors.blue[500]} alt="Shield protection">
1081
- <CustomIcon />
1082
- </Icon>
1083
- ```
1084
-
1085
- #### Props
1086
-
1087
- | Name | Type | Required | Default | Description |
1088
- | -------------- | ------------------------------- | ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
1089
- | `symbol` | `MaterialSymbol` | Conditional | N/A | Name of the Material Symbol to display (e.g., "home", "add"). Uses the `MaterialSymbol` type for type safety. Alternative to providing the icon as a child. |
1090
- | `children` | `ReactNode` \| `MaterialSymbol` | Conditional | N/A | Icon content - either a Material Symbol name (typed as `MaterialSymbol`), an SVG component (ReactNode), or left empty if using `symbol`. |
1091
- | `size` | `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the icon dimensions. |
1092
- | `fill` | `string` | No | N/A | Fill/color of the icon. Default is to inherit from text color or icon defaults. |
1093
- | `alt` | `string` | Conditional | N/A | Alternative text for screen readers. Required if the icon conveys meaning on its own. Omit for decorative icons alongside text. |
1094
- | `theme` | `IconTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
1095
- | `cssOverrides` | `SerializedStyles` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
1096
- | `className` | `string` | No | N/A | Additional class name(s) for the icon. For Material Symbols, combines with `material-symbols`. |
1097
-
1098
- **`size`**
1099
-
1100
- The icon supports three sizes:
1101
-
1102
- - `sm` (small): 20px
1103
- - `md` (medium): 24px
1104
- - `lg` (large): 32px
1105
-
1106
- **`symbol`**
1107
-
1108
- The `symbol` prop provides a convenient way to specify a Material Symbol icon by name (typed as `MaterialSymbol`, e.g., `"home"`, `"add"`, `"close"`). This is the recommended approach for font-based icons. If both `symbol` and `children` are provided, `symbol` takes precedence for Material Symbols.
1109
-
1110
- **`children`**
1111
-
1112
- The Icon component accepts two types of children:
1113
-
1114
- - **MaterialSymbol**: Pass the icon name as a string typed as `MaterialSymbol` (e.g., `"home"`, `"add"`, `"close"`). Requires the Material Symbols font to be loaded. (Prefer using the `symbol` prop for clarity.)
1115
- - **ReactNode** (SVG): Pass an SVG component (e.g., from `@material-design-icons/svg` or a custom SVG component).
1116
-
1117
- **`alt`**
1118
-
1119
- Provide alternative text to describe the icon's meaning for screen readers:
1120
-
1121
- - **When to use**: Icons that convey meaning on their own (status indicators, standalone alerts, informational graphics)
1122
- - **When to omit**: Icons that are purely decorative or appear alongside descriptive text
1123
-
1124
- #### Customisation
1125
-
1126
- We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
1127
-
1128
- **Custom theme**
1129
-
1130
- ```tsx
1131
- import type { IconTheme } from '@guardian/stand/icon';
1132
- import { Icon } from '@guardian/stand/icon';
1133
- import { baseSizing } from '@guardian/stand';
1134
-
1135
- const customTheme: Partial<IconTheme> = {
1136
- shared: {
1137
- display: 'block',
1138
- 'user-select': 'all',
1139
- },
1140
- md: {
1141
- size: baseSizing['size-48-rem'],
1142
- },
1143
- };
1144
-
1145
- const Component = () => (
1146
- <Icon size="md" theme={customTheme}>
1147
- home
1148
- </Icon>
1149
- );
1150
- ```
1151
-
1152
- **CSS overrides**
1153
-
1154
- ```tsx
1155
- import { Icon } from '@guardian/stand/icon';
1156
- import { baseColors, baseSpacing, semanticSizing } from '@guardian/stand';
1157
- import { css } from '@emotion/react';
1158
-
1159
- const customStyles = css`
1160
- padding: ${baseSpacing['4-rem']};
1161
- background-color: ${baseColors.yellow[400]};
1162
- border-radius: ${semanticSizing.border['extra-wide']};
1163
- `;
1164
-
1165
- const Component = () => (
1166
- <Icon size="lg" fill={baseColors.neutral[900]} cssOverrides={customStyles}>
1167
- home
1168
- </Icon>
1169
- );
1170
- ```
1171
-
1172
- #### Icon Custom Component Build
1173
-
1174
- If you're not using React/Emotion, or `@guardian/stand` is not compatible with your project, you can create a custom Icon component using the styles defined in the `IconTheme` type.
1175
-
1176
- You will however be responsible for any additional functionality on top of the styles, for example icon loading, accessibility, etc.
1177
-
1178
- See "Custom Component" heading under the `Icon` component on [codesandbox.io](https://codesandbox.io/p/sandbox/mrzkrw) for an example of a custom component build without React/Emotion.
1179
-
1180
- **`css`**
1181
-
1182
- You can import the Icon styles as CSS from the package, make sure that your build process supports importing CSS from `node_modules`/`package.json`:
1183
-
1184
- ```css
1185
- /* import the icon font and icon styles */
1186
- @import '@guardian/stand/fonts/MaterialSymbolsOutlined.css';
1187
- @import '@guardian/stand/component/icon.css';
1188
-
1189
- /*
1190
- or for scenarios where you have to use relative paths/node_modules directly:
1191
-
1192
- @import 'node_modules/@guardian/stand/dist/fonts/MaterialSymbolsOutlined.css';
1193
- @import 'node_modules/@guardian/stand/dist/style/build/css/component/icon.css';
1194
- */
1195
-
1196
- @import '@guardian/stand/base/spacing.css';
1197
- @import '@guardian/stand/base/colors.css';
1198
-
1199
- .stand-icon {
1200
- display: var(--component-icon-shared-display);
1201
- user-select: var(--component-icon-shared-user-select);
1202
- font-size: var(--component-icon-lg-size);
1203
- }
1204
-
1205
- .stand-icon-font-color {
1206
- color: var(--base-colors-magenta-400);
1207
- }
1208
-
1209
- .stand-icon-svg > svg {
1210
- width: var(--component-icon-lg-size);
1211
- height: var(--component-icon-lg-size);
1212
- }
1213
-
1214
- .stand-icon-svg-color > svg {
1215
- fill: var(--base-colors-magenta-400);
1216
- }
1217
- ```
1218
-
1219
- ```html
1220
- <!-- Material Symbols (font icons) -->
1221
- <span class="material-symbols stand-icon">home</span>
1222
- <span class="material-symbols stand-icon stand-icon-font-color">upload</span>
1223
-
1224
- <!-- SVG icons -->
1225
- <span class="material-symbols stand-icon stand-icon-svg">
1226
- <svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
1227
- </span>
1228
- <span class="material-symbols stand-icon stand-icon-svg stand-icon-svg-color">
1229
- <svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
1230
- </span>
1231
- ```
1232
-
1233
- **TypeScript/JavaScript**
1234
-
1235
- Use the `componentIcon` variable and the `ComponentIcon` type to define your custom styles in TypeScript/JavaScript:
1236
-
1237
- ```ts
1238
- import type { ComponentIcon } from '@guardian/stand'; // if types required
1239
- import { componentIcon, baseColors } from '@guardian/stand';
1240
-
1241
- const iconStyles = `
1242
- display: ${componentIcon.shared.display};
1243
- user-select: ${componentIcon.shared['user-select']};
1244
- font-size: ${componentIcon.lg.size};
1245
- `;
1246
-
1247
- const iconFontColorStyles = `
1248
- ${iconStyles}
1249
- color: ${baseColors.magenta[400]};
1250
- `;
1251
-
1252
- const iconSvgStyles = `
1253
- width: ${componentIcon.lg.size};
1254
- height: ${componentIcon.lg.size};
1255
- `;
1256
-
1257
- const iconSvgColorStyles = `
1258
- ${iconSvgStyles}
1259
- fill: ${baseColors.magenta[400]};
1260
- `;
1261
-
1262
- // e.g. adding to DOM using vanilla JS styles
1263
- document.getElementById('app')!.innerHTML = `
1264
- <h3>
1265
- Using <code>typescript</code>/<code>javascript</code>
1266
- </h3>
1267
- <div>see <code>src/icon/custom.ts</code><div>
1268
- <div style="margin-top: 4px;">Material Symbols</div>
1269
- <div class="container">
1270
- <span class="material-symbols" style="${iconStyles}">home</span>
1271
- <span class="material-symbols" style="${iconFontColorStyles}">upload</span>
1272
- </div>
1273
- <div style="margin-top: 4px;">SVGs</div>
1274
- <div class="container">
1275
- <span class="material-symbols" style="${iconStyles}"><svg style="${iconSvgStyles}"...>...</svg></span>
1276
- <span class="material-symbols" style="${iconFontColorStyles}"><svg style="${iconSvgColorStyles}"...>...</svg></span>
1277
- </div>
1278
- `;
1279
- ```
1280
-
1281
- ### `LinkButton`
1282
-
1283
- An anchor element styled like the Stand `Button` component, based on the React Aria `Link`. It keeps link semantics (`href`, `target`, `rel`) while sharing the same variants and sizes as `Button`.
1284
-
1285
- **When to use**
1286
-
1287
- - Navigational links that should visually align with buttons
1288
- - Cross-page CTAs where a button look-and-feel is preferred
1289
- - Situations requiring disabled styling while preserving link semantics
1290
-
1291
- **Peer dependencies:**
1292
-
1293
- - `@emotion/react`
1294
- - `react`
1295
- - `react-dom`
1296
- - `react-aria-components`
1297
- - `typescript`
1298
-
1299
- See the `peerDependencies` section of `package.json` for compatible versions.
1300
-
1301
- See [link button custom component build](#button-custom-component-build) for usage without React/Emotion.
1302
-
1303
- #### Example usage
1304
-
1305
- See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
1306
-
1307
- ```tsx
1308
- import { LinkButton } from '@guardian/stand/button';
1309
- import type { LinkButtonTheme, LinkButtonProps } from '@guardian/stand/button';
1310
-
1311
- const Example = () => (
1312
- <>
1313
- <LinkButton
1314
- href="/article"
1315
- variant="emphasised-primary"
1316
- size="md"
1317
- target="_blank"
1318
- rel="noreferrer"
1319
- >
1320
- Read article
1321
- </LinkButton>
1322
-
1323
- <LinkButton
1324
- href="/settings"
1325
- variant="neutral-secondary"
1326
- size="sm"
1327
- isDisabled
1328
- >
1329
- Disabled link
1330
- </LinkButton>
1331
- </>
1332
- );
1333
- ```
1334
-
1335
- #### Props
1336
-
1337
- | Name | Type | Required | Default | Description |
1338
- | -------------- | -------------------------------------------------------------------------------------------------- | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
1339
- | `href` | `string` | Yes | N/A | Destination URL for the link. |
1340
- | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the link-button dimensions and typography. |
1341
- | `variant` | `'emphasised-primary'` \| `'emphasised-secondary'` \| `'neutral-primary'` \| `'neutral-secondary'` | No | `'emphasised-primary'` | Chooses the colour scheme and interaction states. |
1342
- | `children` | `ReactNode` | Yes | N/A | Content of the link. |
1343
- | `isDisabled` | `boolean` | No | `false` | Disables interaction and applies disabled styling. |
1344
- | `theme` | `LinkButtonTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
1345
- | `cssOverrides` | `SerializedStyles` \| `SerializedStyles[]` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
1346
- | `className` | `string` | No | N/A | Optional class name forwarded to the root React Aria link. |
1347
- | `...props` | `ReactAria Link` props | No | N/A | All other props from `react-aria-components` `Link` (e.g. `target`, `rel`, `aria-label`, event handlers). |
1348
-
1349
- **`size` and `variant`**
1350
-
1351
- - Sizes: `xs`, `sm`, `md`, `lg`
1352
- - Variants: `emphasised-primary`, `emphasised-secondary`, `neutral-primary`, `neutral-secondary`
1353
-
1354
- #### Customisation
1355
-
1356
- We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
1357
-
1358
- **Custom theme**
1359
-
1360
- ```tsx
1361
- import type { LinkButtonTheme } from '@guardian/stand/button';
1362
- import { LinkButton } from '@guardian/stand/button';
1363
- import { baseColors } from '@guardian/stand';
1364
-
1365
- const customTheme: Partial<LinkButtonTheme> = {
1366
- 'emphasised-primary': {
1367
- shared: {
1368
- backgroundColor: baseColors['cool-purple'][200],
1369
- color: baseColors['cool-purple'][900],
1370
- border: `2px solid ${baseColors['cool-purple'][700]}`,
1371
- ':hover': {
1372
- backgroundColor: baseColors['cool-purple'][300],
1373
- border: `2px solid ${baseColors['cool-purple'][700]}`,
1374
- },
1375
- ':active': {
1376
- backgroundColor: baseColors['cool-purple'][400],
1377
- border: `2px solid ${baseColors['cool-purple'][700]}`,
1378
- },
1379
- },
1380
- },
1381
- };
1382
-
1383
- const Component = () => (
1384
- <LinkButton
1385
- href="/"
1386
- variant="emphasised-primary"
1387
- size="md"
1388
- theme={customTheme}
1389
- >
1390
- Custom Themed LinkButton
1391
- </LinkButton>
1392
- );
1393
- ```
1394
-
1395
- **CSS overrides**
1396
-
1397
- ```tsx
1398
- import { LinkButton } from '@guardian/stand/button';
1399
- import { baseSpacing } from '@guardian/stand';
1400
- import { css } from '@emotion/react';
1401
-
1402
- const customStyles = css`
1403
- width: 100%;
1404
- text-transform: full-width;
1405
- font-variant: small-caps;
1406
- padding-inline: ${baseSpacing['24-rem']};
1407
- `;
1408
-
1409
- const Component = () => (
1410
- <LinkButton
1411
- href="/"
1412
- variant="neutral-primary"
1413
- size="sm"
1414
- cssOverrides={customStyles}
1415
- >
1416
- CSSOverrides LinkButton
1417
- </LinkButton>
1418
- );
1419
- ```
1420
-
1421
- #### LinkButton Custom Component Build
1422
-
1423
- LinkButton shares the same tokens and CSS output as `Button`. Use the [Button custom component build](#button-custom-component-build) guidance to consume the CSS or TypeScript tokens when you need a non-React implementation.
1424
-
1425
- ### Typography
1426
-
1427
- The Typography component provides a convenient way to wrap React elements in a font variant.
1428
-
1429
- **Peer dependencies**
1430
-
1431
- - `@emotion/react`
1432
- - `react`
1433
- - `react-dom`
1434
- - `typescript`
1435
-
1436
- See the `peerDependencies` section of `package.json` for compatible versions.
1437
-
1438
- #### Example usage
1439
-
1440
- ```tsx
1441
- import { Typography } from '@guardian/stand/typography';
1442
-
1443
- /* types, if required */
1444
- import type { Typography, TypographyTheme } from '@guardian/stand/typography';
1445
-
1446
- /* Paragraph element with body-md preset and text children */
1447
- <Typography element="p" variant="body-md">Body text here</Typography>
1448
-
1449
- /* Div with an italic text wrapped inside */
1450
- <Typography element="div" variant="body-md">Some text, with <Typography element="i" variant="body-italic-md">even more text</Typography></Typography>
1451
- ```
1452
-
1453
- #### Props
1454
-
1455
- | Name | Type | Required | Default | Description |
1456
- | -------------- | ------------------ | -------- | --------- | ---------------------------------------------------- |
1457
- | `element` | Various | No | 'span' | HTML element to render with font applied to. |
1458
- | `variant` | Various | No | 'body-md' | Font variant to apply as a CSS style to the element. |
1459
- | `children` | `ReactNode` | No | N/A | Content to render inside the supplied HTML element. |
1460
- | `theme` | `TypographyTheme` | No | N/A | Custom theme overrides for the typography. |
1461
- | `cssOverrides` | `SerializedStyles` | No | N/A | Custom CSS styles for the typography. |
1462
- | `className` | `string` | No | N/A | Additional class name(s) for the typography. |
1463
-
1464
- #### Customisation
1465
-
1466
- **Custom theme**
1467
-
1468
- The `theme` prop allows you to override the color of the text:
1469
-
1470
- ```tsx
1471
- import type { TypographyTheme } from '@guardian/stand/typography';
1472
- import { Typography } from '@guardian/stand/typography';
1473
-
1474
- const customTheme: TypographyTheme = {
1475
- color: 'red',
1476
- };
1477
- const Component = () => (
1478
- <Typography element="p" variant="body-md" theme={customTheme}>
1479
- Text
1480
- </Typography>
1481
- );
1482
- ```
1483
-
1484
- **CSS overrides**
1485
-
1486
- The `cssOverrides` prop allows you to pass custom CSS to the rendered element.
1487
-
1488
- ## Components - Editorial
1489
-
1490
- Specialised components for use in specific editorial use cases.
1491
-
1492
- ### `Byline`
1493
-
1494
- A flexible byline editor component built in ProseMirror and React with usability and accessibility in mind.
1495
-
1496
- **Peer dependencies:**
1497
-
1498
- You'll need to install the following peer dependencies in your project to use the `Byline` component:
1499
-
1500
- - `@emotion/react`
1501
- - `@guardian/prosemirror-invisibles`
1502
- - `prosemirror-dropcursor`
1503
- - `prosemirror-history`
1504
- - `prosemirror-keymap`
1505
- - `prosemirror-model`
1506
- - `prosemirror-state`
1507
- - `prosemirror-view`
1508
- - `react`
1509
- - `react-dom`
1510
- - `typescript`
1511
-
1512
- See the `peerDependencies` section of `package.json` for compatible versions.
1513
-
1514
- **Note:** If you only need the built CSS (`@guardian/stand/component/byline.css`), you don't need to install these dependencies.
1515
-
1516
- #### Usage
41
+ ```bash
42
+ # With pnpm
43
+ pnpm add @guardian/stand
1517
44
 
1518
- ```tsx
1519
- import type { BylineModel } from '@guardian/stand/byline';
1520
- import { Byline } from '@guardian/stand/byline';
45
+ # or with npm
46
+ npm install @guardian/stand
1521
47
 
1522
- const Component = () => {
1523
- const bylineModel: BylineModel = {
1524
- // ...set up your byline model here
1525
- };
1526
- ...
1527
- return (
1528
- <>
1529
- ...
1530
- <Byline initialValue={bylineModel} />
1531
- ...
1532
- </>
1533
- );
1534
- };
48
+ # or with yarn
49
+ yarn add @guardian/stand
1535
50
  ```
1536
51
 
1537
- By itself the `Byline` component is just the editor UI. You will need to set up the ProseMirror editor state, schema, and plugins to get a fully functioning byline editor. See the props and example below for a more complete implementation.
1538
-
1539
- The `BylineModel` type defines the structure of the byline data which is agnostic from any other data structure. You must convert to/from this model when integrating with your application's data structures.
1540
-
1541
- #### Props
1542
-
1543
- See [`BylineProps`](src/byline/Byline.tsx#L41) for the full list of props, usage example can be seen in Storybook.
1544
-
1545
- #### Example
1546
-
1547
- The `ContentByline` component in `flexible-frontend` has a detailed example of how to use the `Byline` component from Stand. See [ContentByline.tsx](https://github.com/guardian/flexible-content/blob/1d537615a18ae24a4a5410a3f945b2b9db1dbb47/flexible-frontend/src/app/components/furniture/content-byline/ContentByline.tsx#L72-L205).
1548
-
1549
- ### TagPicker
1550
-
1551
- #### TagAutocomplete
1552
-
1553
- _Status: Testing_
1554
-
1555
- Part of the overall TagPicker component, the TagAutocomplete provides an accessible
1556
- autocomplete input for selecting tags from a list of options, based on the [React Aria ComboBox](https://react-spectrum.adobe.com/react-aria/ComboBox) component.
1557
-
1558
- **Peer dependencies:**
1559
-
1560
- - `@emotion/react`
1561
- - `react`
1562
- - `react-aria-components`
1563
- - `react-dom`
1564
- - `typescript`
1565
-
1566
- See the `peerDependencies` section of `package.json` for compatible versions.
1567
-
1568
- **Note:** If you only need the built CSS (`@guardian/stand/component/tagAutocomplete.css`), you don't need to install these dependencies.
1569
-
1570
- ##### Props
1571
-
1572
- See [`TagAutocompleteProps`](src/components/tag-picker/TagAutocomplete.tsx#L23) for the full list of props, usage example can be seen in Storybook.
1573
-
1574
- #### TagTable
1575
-
1576
- _Status: Testing_
1577
-
1578
- Part of the overall TagPicker component, the TagTable provides an accessible
1579
- table for displaying tags, with options to add, remove, and reorder tags via drag and drop,
1580
- based on the [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table) component.
1581
-
1582
- **Peer dependencies:**
1583
-
1584
- - `@emotion/react`
1585
- - `react`
1586
- - `react-aria-components`
1587
- - `react-dom`
1588
- - `typescript`
1589
-
1590
- See the `peerDependencies` section of `package.json` for compatible versions.
1591
-
1592
- **Note:** If you only need the built CSS (`@guardian/stand/component/tagTable.css`), you don't need to install these dependencies.
52
+ Depending on your project setup, you may also need to install [peer dependencies](https://nodejs.org/en/blog/npm/peer-dependencies).
1593
53
 
1594
- ##### Props
54
+ **My project uses React, Emotion, and TypeScript:**
1595
55
 
1596
- See [`TagTableProps`](src/components/tag-picker/TagTable.tsx#L31) for the full list of props, usage example can be seen in Storybook.
56
+ See the `peerDependencies` in `package.json` for compatible versions and install them.
1597
57
 
1598
- #### Usage
58
+ If you're using the Tools Design System, you may also want to install the compatible version of [`react-aria-components`](https://react-aria.adobe.com/).
1599
59
 
1600
- _Example with TagAutocomplete and TagTable combined:_
60
+ For specific Editorial Components, check the documentation for each component for additional dependencies.
1601
61
 
1602
- ```tsx
1603
- import { TagAutocomplete, TagTable } from '@guardian/stand/tag-picker';
62
+ **My project doesn't (or can't) use React, Emotion, or TypeScript:**
1604
63
 
1605
- const Component = () => {
1606
- const [selectedTags, setSelectedTags] = useState<
1607
- TagManagerObjectData[] // TagManagerObjectData is an internal type representing a Tag
1608
- >([]);
1609
- const [options, setOptions] = useState<TagManagerObjectData[]>([]);
1610
- const [value, setValue] = useState('');
1611
- const onChange = (inputText: string) => {
1612
- setValue(inputText);
1613
- if (inputText === '') {
1614
- setOptions([]);
1615
- return;
1616
- }
64
+ You can still use design tokens and styles from the Tools Design System. Import [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties) and/or JS design tokens directly from the package. Every component has a "Custom Component Build" section in its documentation showing how to do this.
1617
65
 
1618
- if (inputText === '*') {
1619
- setOptions(exampleTags); // exampleTags is an array of Tags
1620
- return;
1621
- }
66
+ **My project doesn't use JavaScript at all:**
1622
67
 
1623
- // Simple filtering against exampleTags
1624
- const filteredItems = exampleTags.filter((t) =>
1625
- t.internalName.toLowerCase().includes(inputText.toLowerCase()),
1626
- );
1627
- return setOptions(filteredItems);
1628
- };
1629
- return (
1630
- <>
1631
- <div
1632
- css={css`
1633
- display: flex;
1634
- `}
1635
- >
1636
- <TagAutocomplete
1637
- onChange={onChange}
1638
- options={options}
1639
- label="Tags"
1640
- addTag={(tag) =>
1641
- setSelectedTags((tags) => {
1642
- return [...tags, tag];
1643
- })
1644
- }
1645
- loading={false}
1646
- placeholder={''}
1647
- disabled={false}
1648
- value={value}
1649
- />
1650
- <select>
1651
- option>All tags</option>
1652
- </select>
1653
- </div>
1654
- <TagTable rows={selectedTags} filterRows={() => true} />
1655
- </>
1656
- );
1657
- };
1658
- ```
1659
-
1660
- #### Example
1661
-
1662
- This is currently still in testing phase, so a production implementation is not yet available.
1663
-
1664
- ### `UserMenu`
68
+ You can import CSS variables from the package, provided your build process supports importing CSS from `node_modules`. See the "Custom Component Build" section in each component's documentation.
1665
69
 
1666
- The UserMenu component presents a collection of accessibility settings for users to customise their experience of using the application. The current supported settings are:
70
+ ## Usage
1667
71
 
1668
- - "Text Size"
1669
- - "Font Family"
1670
- - "Color scheme"
72
+ See the [Tools Design System](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs) and [Editorial Components](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs) documentation for details.
1671
73
 
1672
- **Peer dependencies:**
74
+ ## Contributing
1673
75
 
1674
- - `@emotion/react`
1675
- - `react`
1676
- - `react-dom`
1677
- - `typescript`
1678
- - `react-aria-components`
76
+ See the [Contributing guidelines](https://guardian.github.io/stand/?path=/docs/contributing--docs) for guidelines on contributing to this project. Project setup and common tasks are listed below.
1679
77
 
1680
- See the `peerDependencies` section of `package.json` for compatible versions.
78
+ ## Getting Started
1681
79
 
1682
- **When to use**
80
+ > If you are looking to **use Stand in your project**, see the [Tools Design System](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs) or [Editorial Components](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs) documentation for installation and usage instructions.
1683
81
 
1684
- - as an application-level "accessibility options" panel
1685
- - as part of a more general options page
82
+ The following steps are for **developing the Stand library itself**.
1686
83
 
1687
- #### Usage
84
+ 1. **Install dependencies:**
1688
85
 
1689
- ```tsx
1690
- import { UserMenu, type UserMenuProps } from '@guardian/stand/user-menu';
1691
-
1692
- const customFontFamilyOptions = [
1693
- {
1694
- id: 'white',
1695
- buttonStyle: {
1696
- backgroundColor: 'white',
1697
- },
1698
- isDefault: true,
1699
- },
1700
- {
1701
- id: 'pink',
1702
- buttonStyle: {
1703
- backgroundColor: 'pink',
1704
- },
1705
- },
1706
- ];
1707
-
1708
- const Component = ({
1709
- currentPreferences,
1710
- updatePreferences,
1711
- }: {
1712
- currentPreferences: UserMenuProps['preferences'];
1713
- updatePreferences: UserMenuProps['updatePreferences'];
1714
- }) => {
1715
- ...
1716
- return (
1717
- <>
1718
- ...
1719
- <UserMenu
1720
- feedBacklink="https://example.com/feedback-form"
1721
- fontFamilyOptions={customFontFamilyOptions}
1722
- colorSchemeOptions={[]}
1723
- preferences={currentPreferences}
1724
- updatePreferences={updatePreferences}
1725
- />
1726
- ...
1727
- </>
1728
- );
1729
- };
1730
- ```
1731
-
1732
- `UserMenu` does not manage the persistence of the settings, nor how they are applied in the application when set - it merely presents a set of [controlled](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components) inputs for displaying and setting the values held by the application.
1733
-
1734
- There are options defaults for each of the setting, but these can be overridden with the props. IF the application does not support customising one of the options, setting an empty array for one of the sets of options will exclude that option from the UI (like in `colorSchemeOptions` in the example above).
86
+ ```bash
87
+ # With pnpm
88
+ pnpm install
89
+ ```
1735
90
 
1736
- #### Props
91
+ 2. **Run Storybook**: Most development is done within Storybook, which provides a live environment for building and testing components in isolation:
1737
92
 
1738
- See [`UserMenuProps`](src/user-menu/UserMenu.tsx#L14) for the full list of props, usage example can be seen in Storybook.
93
+ ```bash
94
+ pnpm storybook
95
+ ```
1739
96
 
1740
- #### Example
97
+ This will start Storybook at `http://localhost:6007`. See the [contributing guidelines](https://guardian.github.io/stand/?path=/docs/contributing--docs) for how to add new components and stories.
1741
98
 
1742
- This is currently still in testing phase, so a production implementation is not yet available.
99
+ 3. **Build** the package for publishing:
1743
100
 
1744
- ### Contributing
101
+ ```bash
102
+ pnpm build
103
+ ```
1745
104
 
1746
- See the [Contributing to Stand](./CONTRIBUTING.md) documentation for guidelines on contributing to this project. Project setup and common tasks are listed below.
105
+ To regenerate Style Dictionary design token outputs after changing any token files (see [Design Tokens and Style Dictionary](#design-tokens-and-style-dictionary)):
1747
106
 
1748
- ### Setup
107
+ ```bash
108
+ pnpm build-styled
109
+ ```
1749
110
 
1750
- - Run `./setup.sh` in the project root (flexible-content) directory to set up pnpm, install dependencies, and build the project.
111
+ 4. **Test:**
1751
112
 
1752
- ### Tasks
113
+ ```bash
114
+ pnpm test # unit tests (Jest)
115
+ pnpm test:e2e # end-to-end tests (Playwright)
116
+ pnpm test:react-matrix # compatibility matrix tests (see Compatibility)
117
+ pnpm tsc # TypeScript type checking
118
+ pnpm lint # lint (pnpm lint:fix to auto-fix)
119
+ pnpm format:check # formatting check (pnpm format:fix to auto-fix)
120
+ ```
1753
121
 
1754
- - Run `pnpm install` to install dependencies.
1755
- - Run `pnpm build` to build, this makes any changes available to flexible-frontend
1756
- - Run `pnpm storybook` to run Storybook
1757
- - Run `pnpm build:storybook` to build the Storybook static site
1758
- - Run `pnpm build-styled` to build the Style Dictionary styles
1759
- - Run `pnpm test` to run tests
1760
- - Run `pnpm test:e2e` to run end-to-end tests using Playwright
1761
- - Run `pnpm test:react-matrix` to run matrix tests (see Compatibility section below)
1762
- - Run `pnpm tsc` to run check TypeScript types
1763
- - Run `pnpm lint` to run the linter
1764
- - Run `pnpm lint:fix` to fix any auto-fixable issues
1765
- - Run `pnpm format:check` to check code formatting
1766
- - Run `pnpm format:fix` to fix code formatting issues
122
+ ## Design Tokens and Style Dictionary
1767
123
 
1768
- ### Style Dictionary
124
+ The [Design Tokens](https://www.w3.org/community/design-tokens/) specification provides standards upon which products and design tools can rely for sharing stylistic pieces of a design system at scale.
1769
125
 
1770
126
  The project uses [Style Dictionary](https://styledictionary.com/) to manage design tokens.
1771
127
 
1772
- Tokens are defined in the `src/styleD/tokens` folder.
128
+ Tokens are defined in `src/styleD/tokens/`, the generated outputs live in `src/styleD/build/`, and both are committed to the repository. During the package build, Rollup copies the build outputs into `dist/styleD/build/` for publishing.
1773
129
 
1774
- The output styles are generated into the `src/styleD/build` folder.
130
+ Most foundation tokens are generated from Figma variables, see `scripts/figma/README.md` for details.
1775
131
 
1776
- We use rollup to copy the built styles into the `dist/styleD/build` folder during the build process, and these are published with the package.
132
+ After making changes to any token files, regenerate the outputs and commit them:
1777
133
 
1778
- Most tokens are generated from Figma variables using a script to make these available in the `src/styleD/tokens` folder. See the [Generate Design Tokens from Figma Variables](./scripts/figma/README.md) documentation for more details.
1779
-
1780
- Use `pnpm build-styled` to generate the styles after making changes to the tokens and make sure to test and commit the changes to the built styles.
1781
-
1782
- See the [Style Dictionary documentation](./docs/style-dictionary.md) for more details on how we structure and generate the styles.
134
+ ```bash
135
+ pnpm build-styled
136
+ ```
1783
137
 
1784
- ### Compatibility
138
+ See the [Style Dictionary documentation](https://guardian.github.io/stand/?path=/docs/style-dictionary--docs) for more details on how we structure and generate the styles.
1785
139
 
1786
- See the package.json `peerDependencies` section for compatible versions of React and other dependencies that Stand works with.
140
+ ## Compatibility
1787
141
 
1788
- Version sets for matrix testing live in `./scripts/deps-matrix-versions.json`:
142
+ See the `peerDependencies` in `package.json` for compatible versions of React and other dependencies.
1789
143
 
1790
- The test script `./scripts/test-deps-matrix.sh` reads this JSON file first, then applies any environment overrides you supply. Precedence is:
144
+ Version sets for matrix testing live in `./scripts/deps-matrix-versions.json`. The test script `./scripts/test-deps-matrix.sh` reads this file first, then applies any environment overrides. Precedence is:
1791
145
 
1792
146
  1. Explicit env var (e.g. `REACT_VERSIONS="18.0.0 19.0.0"`)
1793
147
  2. Value from `deps-matrix-versions.json`
1794
148
 
1795
149
  All three variables (`REACT_VERSIONS`, `EMOTION_VERSIONS`, `TS_VERSIONS`) must be defined after loading; otherwise the script exits with an error.
1796
150
 
1797
- Matrix generation in CI uses the same JSON file in the workflow: `../.github/workflows/stand-component-library-deps-matrix.yml` to ensure consistency.
151
+ Matrix generation in CI uses the same JSON file in the workflow `../.github/workflows/stand-component-library-deps-matrix.yml` to ensure consistency.
1798
152
 
1799
- #### Updating Supported Versions
153
+ ### Updating Supported Versions
1800
154
 
1801
- 1. Edit `./scripts/deps-matrix-versions.json` with new versions
155
+ 1. Edit `./scripts/deps-matrix-versions.json` with new versions.
1802
156
  2. Run the matrix test locally:
1803
157
  ```bash
1804
158
  ./scripts/test-deps-matrix.sh
@@ -1807,13 +161,23 @@ Matrix generation in CI uses the same JSON file in the workflow: `../.github/wor
1807
161
  ```bash
1808
162
  REACT_VERSIONS="18.0.0" EMOTION_VERSIONS="11.14.0" TS_VERSIONS="5.1" ./scripts/test-deps-matrix.sh
1809
163
  ```
1810
- 4. Review results (table output and any failures). Fix issues or adjust code
1811
- 5. Update `peerDependencies` in `package.json` to reflect the new minimum / tested range
1812
- 6. Open a PR, the CI pipeline will comment with the compatibility matrix
1813
-
1814
- #### Tips
1815
-
1816
- - Keep versions in ascending order for readability
1817
- - Remove deprecated versions only after confirming no downstream tool depends on them
1818
- - Add a new version first, then run the matrix, then adjust `peerDependencies` once green
1819
- - Changes to `peerDependencies` are always a breaking change to the library, as per our [recommendations](https://github.com/guardian/recommendations/blob/main/npm-packages.md#changes-to-peerdependencies-ranges-are-breaking)
164
+ 4. Review results (table output and any failures). Fix issues or adjust code.
165
+ 5. Update `peerDependencies` in `package.json` to reflect the new minimum/tested range.
166
+ 6. Open a PR, the CI pipeline will comment with the compatibility matrix.
167
+
168
+ ### Tips
169
+
170
+ - Keep versions in ascending order for readability.
171
+ - Remove deprecated versions only after confirming no downstream tool depends on them.
172
+ - Add a new version first, then run the matrix, then adjust `peerDependencies` once green.
173
+ - Changes to `peerDependencies` are always a breaking change, as per our [recommendations](https://github.com/guardian/recommendations/blob/main/npm-packages.md#changes-to-peerdependencies-ranges-are-breaking).
174
+
175
+ ## Documentation Site Map
176
+
177
+ - [Getting Started](https://guardian.github.io/stand/?path=/docs/getting-started--docs)
178
+ - [Contributing](https://guardian.github.io/stand/?path=/docs/contributing--docs)
179
+ - [Tools Design System Introduction](https://guardian.github.io/stand/?path=/docs/stand-tools-design-system-introduction--docs)
180
+ - [Editorial Components Introduction](https://guardian.github.io/stand/?path=/docs/stand-editorial-components-introduction--docs)
181
+ - [Style Dictionary](https://guardian.github.io/stand/?path=/docs/style-dictionary--docs)
182
+ - [Architecture Decision Records](https://guardian.github.io/stand/?path=/docs/architecture-decision-records--docs)
183
+ - [Changelog](https://guardian.github.io/stand/?path=/docs/changelog--docs)