@guardian/stand 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +1148 -10
  2. package/dist/avatar.cjs +9 -0
  3. package/dist/avatar.js +2 -0
  4. package/dist/button.cjs +9 -0
  5. package/dist/button.js +2 -0
  6. package/dist/byline.cjs +9 -0
  7. package/dist/byline.js +2 -0
  8. package/dist/components/TitleText.cjs +28 -0
  9. package/dist/components/TitleText.js +22 -0
  10. package/dist/components/avatar/Avatar.cjs +57 -0
  11. package/dist/components/avatar/Avatar.js +27 -0
  12. package/dist/components/avatar/styles.cjs +33 -0
  13. package/dist/components/avatar/styles.js +29 -0
  14. package/dist/components/avatar/types.cjs +16 -0
  15. package/dist/components/avatar/types.js +14 -0
  16. package/dist/components/button/Button.cjs +29 -0
  17. package/dist/components/button/Button.js +14 -0
  18. package/dist/components/button/styles.cjs +58 -0
  19. package/dist/components/button/styles.js +53 -0
  20. package/dist/components/byline/styles.cjs +5 -5
  21. package/dist/components/byline/styles.js +1 -1
  22. package/dist/components/icon/Icon.cjs +46 -0
  23. package/dist/components/icon/Icon.js +19 -0
  24. package/dist/components/icon/styles.cjs +27 -0
  25. package/dist/components/icon/styles.js +20 -0
  26. package/dist/components/link-button/LinkButton.cjs +29 -0
  27. package/dist/components/link-button/LinkButton.js +14 -0
  28. package/dist/components/link-button/styles.cjs +9 -0
  29. package/dist/components/link-button/styles.js +6 -0
  30. package/dist/components/tag-picker/styles.cjs +11 -11
  31. package/dist/components/tag-picker/styles.js +1 -1
  32. package/dist/components/typography/Typography.cjs +26 -0
  33. package/dist/components/typography/Typography.js +13 -0
  34. package/dist/components/typography/styles.cjs +15 -0
  35. package/dist/components/typography/styles.js +12 -0
  36. package/dist/components/user-menu/PreferenceRadioGroup.cjs +53 -0
  37. package/dist/components/user-menu/PreferenceRadioGroup.js +19 -0
  38. package/dist/components/user-menu/UserMenu.cjs +67 -0
  39. package/dist/components/user-menu/UserMenu.js +11 -0
  40. package/dist/components/user-menu/default-options.cjs +109 -0
  41. package/dist/components/user-menu/default-options.js +105 -0
  42. package/dist/components/user-menu/styles.cjs +90 -0
  43. package/dist/components/user-menu/styles.js +83 -0
  44. package/dist/fonts/MaterialSymbolsOutlined.css +23 -0
  45. package/dist/fonts/MaterialSymbolsRound.css +23 -0
  46. package/dist/fonts/MaterialSymbolsSharp.css +23 -0
  47. package/dist/fonts/material-symbols-types.ts +3825 -0
  48. package/dist/icon.cjs +9 -0
  49. package/dist/icon.js +2 -0
  50. package/dist/index.cjs +22 -13
  51. package/dist/index.js +9 -4
  52. package/dist/link-button.cjs +7 -0
  53. package/dist/link-button.js +1 -0
  54. package/dist/styleD/build/css/{css/base → base}/colors.css +1 -1
  55. package/dist/styleD/build/css/{css/base → base}/sizing.css +4 -0
  56. package/dist/styleD/build/css/{css/base → base}/spacing.css +4 -0
  57. package/dist/styleD/build/css/{css/base → base}/typography.css +1 -0
  58. package/dist/styleD/build/css/component/avatar.css +54 -0
  59. package/dist/styleD/build/css/component/button.css +218 -0
  60. package/dist/styleD/build/css/{css/component → component}/byline.css +1 -1
  61. package/dist/styleD/build/css/component/icon.css +11 -0
  62. package/dist/styleD/build/css/component/typography.css +7 -0
  63. package/dist/styleD/build/css/component/userMenu.css +29 -0
  64. package/dist/styleD/build/css/semantic/colors.css +69 -0
  65. package/dist/styleD/build/css/{css/semantic → semantic}/sizing.css +2 -0
  66. package/dist/styleD/build/css/{css/semantic → semantic}/typography.css +9 -0
  67. package/dist/styleD/build/typescript/base/colors.cjs +1 -1
  68. package/dist/styleD/build/typescript/base/colors.js +1 -1
  69. package/dist/styleD/build/typescript/base/sizing.cjs +4 -0
  70. package/dist/styleD/build/typescript/base/sizing.js +4 -0
  71. package/dist/styleD/build/typescript/base/spacing.cjs +4 -0
  72. package/dist/styleD/build/typescript/base/spacing.js +4 -0
  73. package/dist/styleD/build/typescript/base/typography.cjs +2 -1
  74. package/dist/styleD/build/typescript/base/typography.js +2 -1
  75. package/dist/styleD/build/typescript/component/avatar.cjs +88 -0
  76. package/dist/styleD/build/typescript/component/avatar.js +86 -0
  77. package/dist/styleD/build/typescript/component/button.cjs +331 -0
  78. package/dist/styleD/build/typescript/component/button.js +329 -0
  79. package/dist/styleD/build/typescript/component/byline.cjs +1 -1
  80. package/dist/styleD/build/typescript/component/byline.js +1 -1
  81. package/dist/styleD/build/typescript/component/icon.cjs +19 -0
  82. package/dist/styleD/build/typescript/component/icon.js +17 -0
  83. package/dist/styleD/build/typescript/component/typography.cjs +7 -0
  84. package/dist/styleD/build/typescript/component/typography.js +5 -0
  85. package/dist/styleD/build/typescript/component/userMenu.cjs +37 -0
  86. package/dist/styleD/build/typescript/component/userMenu.js +35 -0
  87. package/dist/styleD/build/typescript/semantic/colors.cjs +47 -5
  88. package/dist/styleD/build/typescript/semantic/colors.js +47 -5
  89. package/dist/styleD/build/typescript/semantic/sizing.cjs +3 -1
  90. package/dist/styleD/build/typescript/semantic/sizing.js +3 -1
  91. package/dist/styleD/build/typescript/semantic/typography.cjs +15 -0
  92. package/dist/styleD/build/typescript/semantic/typography.js +15 -0
  93. package/dist/tag-picker.cjs +13 -0
  94. package/dist/tag-picker.js +4 -0
  95. package/dist/types/avatar.d.ts +19 -0
  96. package/dist/types/button.d.ts +20 -0
  97. package/dist/types/byline.d.ts +25 -0
  98. package/dist/types/components/TitleText.d.ts +6 -0
  99. package/dist/types/components/avatar/Avatar.d.ts +2 -0
  100. package/dist/types/components/avatar/styles.d.ts +8 -0
  101. package/dist/types/components/avatar/types.d.ts +45 -0
  102. package/dist/types/components/button/Button.d.ts +2 -0
  103. package/dist/types/components/button/styles.d.ts +7 -0
  104. package/dist/types/components/button/types.d.ts +13 -0
  105. package/dist/types/components/byline/Byline.d.ts +1 -1
  106. package/dist/types/components/byline/schema.d.ts +1 -1
  107. package/dist/types/components/byline/styles.d.ts +1 -1
  108. package/dist/types/components/byline/theme.d.ts +1 -1
  109. package/dist/types/components/icon/Icon.d.ts +2 -0
  110. package/dist/types/components/icon/styles.d.ts +8 -0
  111. package/dist/types/components/icon/types.d.ts +28 -0
  112. package/dist/types/components/link-button/LinkButton.d.ts +2 -0
  113. package/dist/types/components/link-button/styles.d.ts +330 -0
  114. package/dist/types/components/link-button/types.d.ts +13 -0
  115. package/dist/types/components/tag-picker/TagAutocomplete.d.ts +1 -1
  116. package/dist/types/components/tag-picker/TagTable.d.ts +1 -1
  117. package/dist/types/components/tag-picker/styles.d.ts +1 -1
  118. package/dist/types/components/typography/Typography.d.ts +6 -0
  119. package/dist/types/components/typography/styles.d.ts +7 -0
  120. package/dist/types/components/typography/types.d.ts +13 -0
  121. package/dist/types/components/user-menu/PreferenceRadioGroup.d.ts +15 -0
  122. package/dist/types/components/user-menu/UserMenu.d.ts +17 -0
  123. package/dist/types/components/user-menu/default-options.d.ts +5 -0
  124. package/dist/types/components/user-menu/model.d.ts +9 -0
  125. package/dist/types/components/user-menu/styles.d.ts +9 -0
  126. package/dist/types/components/user-menu/theme.d.ts +3 -0
  127. package/dist/types/components/user-menu/types.d.ts +5 -0
  128. package/dist/types/fonts/material-symbols-types.d.ts +3822 -0
  129. package/dist/types/icon.d.ts +27 -0
  130. package/dist/types/index.d.ts +25 -7
  131. package/dist/types/link-button.d.ts +20 -0
  132. package/dist/types/styleD/build/typescript/base/colors.d.ts +1 -1
  133. package/dist/types/styleD/build/typescript/base/sizing.d.ts +4 -0
  134. package/dist/types/styleD/build/typescript/base/spacing.d.ts +4 -0
  135. package/dist/types/styleD/build/typescript/base/typography.d.ts +1 -0
  136. package/dist/types/styleD/build/typescript/component/avatar.d.ts +88 -0
  137. package/dist/types/styleD/build/typescript/component/button.d.ts +331 -0
  138. package/dist/types/styleD/build/typescript/component/icon.d.ts +19 -0
  139. package/dist/types/styleD/build/typescript/component/typography.d.ts +7 -0
  140. package/dist/types/styleD/build/typescript/component/userMenu.d.ts +37 -0
  141. package/dist/types/styleD/build/typescript/semantic/colors.d.ts +45 -3
  142. package/dist/types/styleD/build/typescript/semantic/sizing.d.ts +2 -0
  143. package/dist/types/styleD/build/typescript/semantic/typography.d.ts +15 -0
  144. package/dist/types/tag-picker.d.ts +21 -0
  145. package/dist/types/typography.d.ts +19 -0
  146. package/dist/types/user-menu.d.ts +18 -0
  147. package/dist/types/util/mergeDeep.d.ts +1 -0
  148. package/dist/types/util/reset.d.ts +1 -0
  149. package/dist/types/util/storybookStyles.d.ts +1 -0
  150. package/dist/types/util/types.d.ts +34 -0
  151. package/dist/types/utils.d.ts +7 -0
  152. package/dist/typography.cjs +9 -0
  153. package/dist/typography.js +2 -0
  154. package/dist/user-menu.cjs +9 -0
  155. package/dist/user-menu.js +2 -0
  156. package/dist/util/css/reset.css +124 -0
  157. package/dist/util/reset.cjs +10 -0
  158. package/dist/util/reset.css.cjs +5 -0
  159. package/dist/util/reset.css.js +3 -0
  160. package/dist/util/reset.js +8 -0
  161. package/dist/utils.cjs +10 -0
  162. package/dist/utils.js +2 -0
  163. package/package.json +112 -3
  164. package/dist/styleD/build/css/css/semantic/colors.css +0 -29
  165. package/dist/types/components/util.d.ts +0 -4
  166. /package/dist/styleD/build/css/{css/base → base}/radius.css +0 -0
  167. /package/dist/styleD/build/css/{css/component → component}/tagAutocomplete.css +0 -0
  168. /package/dist/styleD/build/css/{css/component → component}/tagTable.css +0 -0
  169. /package/dist/{components/util.cjs → util/mergeDeep.cjs} +0 -0
  170. /package/dist/{components/util.js → util/mergeDeep.js} +0 -0
package/README.md CHANGED
@@ -171,12 +171,11 @@ _Status: WIP_
171
171
 
172
172
  ```ts
173
173
  import { css } from '@emotion/react';
174
+ import { semanticColors, semanticTypography } from '@guardian/stand'; // JS/TS usage
174
175
  import {
175
- semanticColors,
176
- semanticTypography,
177
176
  convertTypographyToEmotionObjectStyle, // helper function to convert from typography token object to emotion CSS object style
178
177
  convertTypographyToEmotionStringStyle, // helper function to convert from typography token object to emotion CSS string style
179
- } from '@guardian/stand'; // JS/TS usage
178
+ } from '@guardian/stand/utils'; // Utils for working with design tokens
180
179
  import '@guardian/stand/semantic/typography.css'; // CSS usage
181
180
 
182
181
  /* JS/TS usage */
@@ -454,7 +453,1048 @@ For a list of the available base/primitives radius tokens see the [Storybook Bas
454
453
 
455
454
  For a full list of CSS Base/Primitives Radius tokens see [`base/radius.css`](./src/styleD/build/css/base/radius.css).
456
455
 
457
- ## Components
456
+ ## Components - Base
457
+
458
+ General purpose components for use across a variety of editorial tools based on the design system.
459
+
460
+ ### `Avatar`
461
+
462
+ The Avatar component displays a user's profile image or initials in a circular container. It supports multiple sizes, colors, and automatic fallback handling.
463
+
464
+ **When to use**
465
+
466
+ - Display user profiles in lists, comments, or headers
467
+ - Show user identity in messaging interfaces
468
+ - Represent users in collaborative features
469
+
470
+ **Peer dependencies:**
471
+
472
+ - `@emotion/react`
473
+ - `react`
474
+ - `react-dom`
475
+ - `typescript`
476
+
477
+ See the `peerDependencies` section of `package.json` for compatible versions.
478
+
479
+ See [avatar custom component build](#avatar-custom-component-build) for usage without React/Emotion.
480
+
481
+ #### Example usage
482
+
483
+ See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/guardian-stand-avatar-component-mqvzh5)
484
+
485
+ ```tsx
486
+ import { Avatar } from '@guardian/stand/avatar';
487
+
488
+ /* types, if required */
489
+ import type { AvatarProps, AvatarTheme } from '@guardian/stand/avatar';
490
+
491
+ /* Initials only */
492
+ <Avatar initials="AB" size="md" />
493
+
494
+ /* Image with alt text */
495
+ <Avatar
496
+ src="https://example.com/avatar.jpg"
497
+ alt="User Name"
498
+ size="md"
499
+ />
500
+
501
+ /* Image with fallback initials and a specific color */
502
+ <Avatar
503
+ src="https://example.com/avatar.jpg"
504
+ alt="User Name"
505
+ initials="AB"
506
+ color="blue"
507
+ size="md"
508
+ />
509
+ ```
510
+
511
+ #### Props
512
+
513
+ | Name | Type | Required | Default | Description |
514
+ | -------------- | ------------------ | ----------- | ------------------------------- | ---------------------------------------------- |
515
+ | `size` | `'sm'` \| `'md'` | No | `'md'` | Size of the avatar. |
516
+ | `color` | Various | No | Deterministic based on initials | Color of the avatar. |
517
+ | `initials` | `string` | Conditional | N/A | Initials to display when no image is provided. |
518
+ | `src` | `string` | Conditional | N/A | URL of the avatar image. |
519
+ | `alt` | `string` | Conditional | N/A | Alt text for the image for accessibility. |
520
+ | `theme` | `AvatarTheme` | No | N/A | Custom theme overrides for the avatar. |
521
+ | `cssOverrides` | `SerializedStyles` | No | N/A | Custom CSS styles for the avatar. |
522
+ | `className` | `string` | No | N/A | Additional class name(s) for the avatar. |
523
+
524
+ When using `src`, the `alt` prop is required for accessibility.
525
+ When not using `src`, the `initials` prop is required.
526
+
527
+ **`size`**
528
+
529
+ The avatar supports two sizes:
530
+
531
+ - `sm` (small): 32px
532
+ - `md` (medium): 40px
533
+
534
+ **`color`**
535
+
536
+ 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.
537
+
538
+ Available colors: `outlined`, `blue`, `green`, `red`, `cyan`, `teal`, `cool-purple`, `warm-purple`, `magenta`, `orange`, `yellow`
539
+
540
+ #### Customisation
541
+
542
+ We recommend using the Avatar component as provided, but it can be customised using the `theme` or `cssOverrides` props as required.
543
+
544
+ **Custom theme**
545
+
546
+ The `theme` prop allows you to override specific design tokens for the Avatar component:
547
+
548
+ ```tsx
549
+ import type { AvatarTheme } from '@guardian/stand/avatar';
550
+ import { Avatar } from '@guardian/stand/avatar';
551
+ import { baseColors, baseSizing } from '@guardian/stand';
552
+
553
+ const customTheme: AvatarTheme = {
554
+ shared: {
555
+ color: {
556
+ blue: {
557
+ background: baseColors.blue[100],
558
+ text: baseColors.neutral[900],
559
+ },
560
+ },
561
+ },
562
+ md: {
563
+ size: baseSizing['size-48-rem'],
564
+ },
565
+ };
566
+
567
+ const Component = () => (
568
+ <Avatar color="blue" initials="CT" size="md" theme={customTheme} />
569
+ );
570
+ ```
571
+
572
+ **CSS overrides**
573
+
574
+ The `cssOverrides` prop allows you to pass custom CSS to the Avatar component, if the theme prop is not sufficient:
575
+
576
+ ```tsx
577
+ import { Avatar } from '@guardian/stand/avatar';
578
+ import { baseColors, baseSizing, baseSpacing } from '@guardian/stand';
579
+ import { css } from '@emotion/react';
580
+
581
+ const customStyles = css`
582
+ border: ${baseSizing['size-2-rem']} solid ${baseColors.red[500]};
583
+ margin: ${baseSpacing['8-rem']};
584
+ `;
585
+
586
+ const Component = () => (
587
+ <Avatar initials="CO" size="md" color="red" cssOverrides={customStyles} />
588
+ );
589
+ ```
590
+
591
+ #### Avatar Custom Component Build
592
+
593
+ 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.
594
+
595
+ You will however be responsible for any additional functionality on top of the styles, for example image loading, image fallback, accessibility etc.
596
+
597
+ See "Custom Component" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/guardian-stand-avatar-component-mqvzh5)
598
+
599
+ **`css`**
600
+
601
+ 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`:
602
+
603
+ ```css
604
+ /* import the font and avatar styles */
605
+ @import '@guardian/stand/fonts/OpenSans.css';
606
+ @import '@guardian/stand/component/avatar.css';
607
+
608
+ /*
609
+ or for scenarios where you have to use relative paths/node_modules directly:
610
+
611
+ @import 'node_modules/@guardian/stand/dist/fonts/OpenSans.css';
612
+ @import 'node_modules/@guardian/stand/dist/styleD/build/css/component/avatar.css';
613
+ */
614
+
615
+ /* example setup of avatar style using md size and blue color */
616
+ .stand-avatar {
617
+ display: var(--component-avatar-shared-display);
618
+ align-items: var(--component-avatar-shared-align-items);
619
+ justify-content: var(--component-avatar-shared-justify-content);
620
+ overflow: var(--component-avatar-shared-overflow);
621
+ flex-shrink: var(--component-avatar-shared-flex-shrink);
622
+ border-radius: var(--component-avatar-shared-border-radius);
623
+ background-color: var(--component-avatar-shared-color-blue-background);
624
+ width: var(--component-avatar-md-size);
625
+ height: var(--component-avatar-md-size);
626
+ border: var(--component-avatar-shared-color-blue-border);
627
+ color: var(--component-avatar-shared-color-blue-text);
628
+ font: var(--component-avatar-md-typography-font);
629
+ letter-spacing: var(--component-avatar-md-typography-letter-spacing);
630
+ font-variation-settings: 'wdth'
631
+ var(--component-avatar-md-typography-font-width);
632
+ }
633
+
634
+ /* example setup for avatar image */
635
+ .stand-avatar > img {
636
+ width: 100%;
637
+ height: 100%;
638
+ object-fit: cover;
639
+ }
640
+ ```
641
+
642
+ ```html
643
+ <!-- example usage of avatar style in html with avatar -->
644
+ <div class="stand-avatar">AB</div>
645
+
646
+ <!-- example usage of avatar style in html with avatar image -->
647
+ <div class="stand-avatar">
648
+ <img src="https://example.com/avatar.jpg" alt="User Name" />
649
+ </div>
650
+ ```
651
+
652
+ **TypeScript/JavaScript**
653
+
654
+ Use the `componentAvatar` variable and the `ComponentAvatar` type to define your custom styles in TypeScript/JavaScript:
655
+
656
+ ```ts
657
+ import type { ComponentAvatar } from '@guardian/stand'; // if types required
658
+ import { componentAvatar } from '@guardian/stand';
659
+
660
+ const style = `
661
+ display: ${componentAvatar.shared.display};
662
+ align-items: ${componentAvatar.shared['align-items']};
663
+ justify-content: ${componentAvatar.shared['justify-content']};
664
+ overflow: ${componentAvatar.shared.overflow};
665
+ flex-shrink: ${componentAvatar.shared['flex-shrink']};
666
+ border-radius: ${componentAvatar.shared['border-radius']};
667
+ background-color: ${componentAvatar.shared.color.blue.background};
668
+ width: ${componentAvatar.md.size};
669
+ height: ${componentAvatar.md.size};
670
+ border: ${componentAvatar.shared.color.blue.border};
671
+ color: ${componentAvatar.shared.color.blue.text};
672
+ font: ${componentAvatar.md.typography.font};
673
+ letter-spacing: ${componentAvatar.md.typography.letterSpacing};
674
+ font-variation-settings: 'wdth' ${componentAvatar.md.typography.fontWidth};
675
+ `;
676
+
677
+ const imgStyle = `
678
+ width: 100%;
679
+ height: 100%;
680
+ object-fit: cover;
681
+ `;
682
+
683
+ // e.g. adding to DOM using vanilla JS styles
684
+ document.getElementById('app')!.innerHTML = `
685
+ <h2>
686
+ Using <code>typescript</code>/<code>javascript</code>
687
+ </h2>
688
+ <div style="${style}">AB</div>
689
+ <div style="${style}">
690
+ <img
691
+ style="${imgStyle}"
692
+ src="https://example.com/avatar.jpg"
693
+ alt="User Name"
694
+ />
695
+ </div>
696
+ `;
697
+ ```
698
+
699
+ ### `Button`
700
+
701
+ 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.
702
+
703
+ **When to use**
704
+
705
+ - Primary and secondary actions that require clear affordance
706
+ - Form submissions or confirm/cancel flows
707
+ - Re-usable button styles that align with Stand tokens
708
+
709
+ **Peer dependencies:**
710
+
711
+ - `@emotion/react`
712
+ - `react`
713
+ - `react-dom`
714
+ - `react-aria-components`
715
+ - `typescript`
716
+
717
+ See the `peerDependencies` section of `package.json` for compatible versions.
718
+
719
+ See [button custom component build](#button-custom-component-build) for usage without React/Emotion.
720
+
721
+ #### Example usage
722
+
723
+ See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
724
+
725
+ ```tsx
726
+ import { Button } from '@guardian/stand/button';
727
+ import type { ButtonProps, ButtonTheme } from '@guardian/stand/button';
728
+
729
+ const Example = () => (
730
+ <>
731
+ <Button
732
+ variant="emphasised-primary"
733
+ size="md"
734
+ onPress={() => {
735
+ console.log('Primary action');
736
+ }}
737
+ >
738
+ Publish
739
+ </Button>
740
+
741
+ <Button variant="neutral-secondary" size="sm" isDisabled>
742
+ Disabled
743
+ </Button>
744
+ </>
745
+ );
746
+ ```
747
+
748
+ #### Props
749
+
750
+ | Name | Type | Required | Default | Description |
751
+ | -------------- | -------------------------------------------------------------------------------------------------- | -------- | ---------------------- | ------------------------------------------------------------------------------------------------- |
752
+ | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the button dimensions and typography. |
753
+ | `variant` | `'emphasised-primary'` \| `'emphasised-secondary'` \| `'neutral-primary'` \| `'neutral-secondary'` | No | `'emphasised-primary'` | Chooses the colour scheme and interaction states. |
754
+ | `children` | `ReactNode` \| `RenderProps` | Yes | N/A | Content inside the button. Render props receive `{ isPending }` from React Aria. |
755
+ | `isDisabled` | `boolean` | No | `false` | Disables interaction and applies disabled styling. |
756
+ | `theme` | `ButtonTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
757
+ | `cssOverrides` | `SerializedStyles` \| `SerializedStyles[]` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
758
+ | `className` | `string` | No | N/A | Optional class name forwarded to the root React Aria button. |
759
+ | `...props` | `ReactAria Button` props | No | N/A | All other props from `react-aria-components` `Button` (e.g. `type`, `autoFocus`, event handlers). |
760
+
761
+ **`size` and `variant`**
762
+
763
+ - Sizes: `xs`, `sm`, `md`, `lg`
764
+ - Variants: `emphasised-primary`, `emphasised-secondary`, `neutral-primary`, `neutral-secondary`
765
+
766
+ #### Customisation
767
+
768
+ We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
769
+
770
+ **Custom theme**
771
+
772
+ ```tsx
773
+ import type { ButtonTheme } from '@guardian/stand/button';
774
+ import { Button } from '@guardian/stand/button';
775
+ import { baseColors } from '@guardian/stand';
776
+
777
+ const customTheme: Partial<ButtonTheme> = {
778
+ 'emphasised-primary': {
779
+ shared: {
780
+ backgroundColor: baseColors['cool-purple'][200],
781
+ color: baseColors['cool-purple'][900],
782
+ border: `2px solid ${baseColors['cool-purple'][700]}`,
783
+ ':hover': {
784
+ backgroundColor: baseColors['cool-purple'][300],
785
+ border: `2px solid ${baseColors['cool-purple'][700]}`,
786
+ },
787
+ ':active': {
788
+ backgroundColor: baseColors['cool-purple'][400],
789
+ border: `2px solid ${baseColors['cool-purple'][700]}`,
790
+ },
791
+ },
792
+ },
793
+ };
794
+
795
+ const Component = () => (
796
+ <Button variant="emphasised-primary" size="md" theme={customTheme}>
797
+ Custom Themed Button
798
+ </Button>
799
+ );
800
+ ```
801
+
802
+ **CSS overrides**
803
+
804
+ ```tsx
805
+ import { Button } from '@guardian/stand/button';
806
+ import { baseSpacing } from '@guardian/stand';
807
+ import { css } from '@emotion/react';
808
+
809
+ const customStyles = css`
810
+ width: 100%;
811
+ text-transform: full-width;
812
+ font-variant: small-caps;
813
+ padding-inline: ${baseSpacing['24-rem']};
814
+ `;
815
+
816
+ const Component = () => (
817
+ <Button variant="neutral-primary" size="sm" cssOverrides={customStyles}>
818
+ CSSOverrides Button
819
+ </Button>
820
+ );
821
+ ```
822
+
823
+ #### Button Custom Component Build
824
+
825
+ 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.
826
+
827
+ You will however be responsible for any additional functionality on top of the styles, for example accessibility, focus management, interaction states etc.
828
+
829
+ See "Custom Component" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
830
+
831
+ **`css`**
832
+
833
+ 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`:
834
+
835
+ ```css
836
+ /* import the font and button variables */
837
+ @import '@guardian/stand/fonts/OpenSans.css';
838
+ @import '@guardian/stand/component/button.css';
839
+
840
+ /*
841
+ or for scenarios where you have to use relative paths/node_modules directly:
842
+
843
+ @import 'node_modules/@guardian/stand/dist/fonts/OpenSans.css';
844
+ @import 'node_modules/@guardian/stand/dist/styleD/build/css/component/button.css';
845
+ */
846
+
847
+ /* shared button styles for all variants */
848
+ .stand-button {
849
+ display: var(--component-button-shared-display);
850
+ -webkit-appearance: var(--component-button-shared-webkit-appearance);
851
+ text-align: var(--component-button-shared-text-align);
852
+ box-shadow: var(--component-button-shared-box-shadow);
853
+ text-decoration: var(--component-button-shared-text-decoration);
854
+ cursor: var(--component-button-shared-cursor);
855
+ justify-content: var(--component-button-shared-justify-content);
856
+ align-items: var(--component-button-shared-align-items);
857
+ }
858
+ .stand-button:focus-visible {
859
+ outline: var(--component-button-shared-focus-visible-outline);
860
+ outline-offset: var(--component-button-shared-focus-visible-outline-offset);
861
+ }
862
+ .stand-button:disabled {
863
+ cursor: var(--component-button-shared-disabled-cursor);
864
+ }
865
+
866
+ /* example setup of button/link button style using md size and emphasised primary variant */
867
+ .stand-button-emphasised-primary {
868
+ color: var(--component-button-emphasised-primary-shared-color);
869
+ background: var(
870
+ --component-button-emphasised-primary-shared-background-color
871
+ );
872
+ height: var(--component-button-emphasised-primary-md-height);
873
+ padding: var(--component-button-emphasised-primary-md-padding-top)
874
+ var(--component-button-emphasised-primary-md-padding-right)
875
+ var(--component-button-emphasised-primary-md-padding-bottom)
876
+ var(--component-button-emphasised-primary-md-padding-left);
877
+ font: var(--component-button-emphasised-primary-md-typography-font);
878
+ letter-spacing: var(
879
+ --component-button-emphasised-primary-md-typography-letter-spacing
880
+ );
881
+ font-variation-settings: 'wdth'
882
+ var(--component-button-emphasised-primary-md-typography-font-width);
883
+ border: var(--component-button-emphasised-primary-shared-border);
884
+ border-radius: var(
885
+ --component-button-emphasised-primary-shared-border-radius
886
+ );
887
+ }
888
+ .stand-button-emphasised-primary:hover {
889
+ background: var(
890
+ --component-button-emphasised-primary-shared-hover-background-color
891
+ );
892
+ border: var(--component-button-emphasised-primary-shared-hover-border);
893
+ }
894
+ .stand-button-emphasised-primary:active {
895
+ background: var(
896
+ --component-button-emphasised-primary-shared-active-background-color
897
+ );
898
+ border: var(--component-button-emphasised-primary-shared-active-border);
899
+ }
900
+ .stand-button-emphasised-primary:disabled {
901
+ color: var(--component-button-emphasised-primary-shared-disabled-color);
902
+ background: var(
903
+ --component-button-emphasised-primary-shared-disabled-background-color
904
+ );
905
+ border: var(--component-button-emphasised-primary-shared-disabled-border);
906
+ }
907
+
908
+ /* example setup of button/link button style using md size and neutral secondary variant */
909
+ .stand-button-neutral-secondary {
910
+ color: var(--component-button-neutral-secondary-shared-color);
911
+ background: var(
912
+ --component-button-neutral-secondary-shared-background-color
913
+ );
914
+ height: var(--component-button-neutral-secondary-md-height);
915
+ padding: var(--component-button-neutral-secondary-md-padding-top)
916
+ var(--component-button-neutral-secondary-md-padding-right)
917
+ var(--component-button-neutral-secondary-md-padding-bottom)
918
+ var(--component-button-neutral-secondary-md-padding-left);
919
+ font: var(--component-button-neutral-secondary-md-typography-font);
920
+ letter-spacing: var(
921
+ --component-button-neutral-secondary-md-typography-letter-spacing
922
+ );
923
+ font-variation-settings: 'wdth'
924
+ var(--component-button-neutral-secondary-md-typography-font-width);
925
+ border: var(--component-button-neutral-secondary-shared-border);
926
+ border-radius: var(
927
+ --component-button-neutral-secondary-shared-border-radius
928
+ );
929
+ }
930
+ .stand-button-neutral-secondary:hover {
931
+ background: var(
932
+ --component-button-neutral-secondary-shared-hover-background-color
933
+ );
934
+ border: var(--component-button-neutral-secondary-shared-hover-border);
935
+ }
936
+ .stand-button-neutral-secondary:active {
937
+ background: var(
938
+ --component-button-neutral-secondary-shared-active-background-color
939
+ );
940
+ border: var(--component-button-neutral-secondary-shared-active-border);
941
+ }
942
+ .stand-button-neutral-secondary:disabled {
943
+ color: var(--component-button-neutral-secondary-shared-disabled-color);
944
+ background: var(
945
+ --component-button-neutral-secondary-shared-disabled-background-color
946
+ );
947
+ border: var(--component-button-neutral-secondary-shared-disabled-border);
948
+ }
949
+ ```
950
+
951
+ ```html
952
+ <button class="stand-button stand-button-emphasised-primary">
953
+ Button Label
954
+ </button>
955
+ <button class="stand-button stand-button-emphasised-primary" disabled>
956
+ Disabled Button Label
957
+ </button>
958
+ <a class="stand-button stand-button-neutral-secondary" href="#"
959
+ >LinkButton Label</a
960
+ >
961
+ ```
962
+
963
+ **TypeScript/JavaScript**
964
+
965
+ Use the `componentButton` variable and the `ComponentButton` type to define your custom styles in TypeScript/JavaScript:
966
+
967
+ ```ts
968
+ import type { ComponentButton } from '@guardian/stand'; // if types required
969
+ import { componentButton } from '@guardian/stand/button';
970
+
971
+ /* NB: The HTML style attribute cannot target psuedo selectors, so they haven't been implemented e.g. hover/focus */
972
+ const sharedButtonStyles = `
973
+ display: ${componentButton.shared.display};
974
+ -webkit-appearance: ${componentButton.shared['-webkit-appearance']};
975
+ text-align: ${componentButton.shared['text-align']};
976
+ box-shadow: ${componentButton.shared['box-shadow']};
977
+ text-decoration: ${componentButton.shared['text-decoration']};
978
+ cursor: ${componentButton.shared.cursor};
979
+ justify-content: ${componentButton.shared['justify-content']};
980
+ align-items: ${componentButton.shared['align-items']};
981
+ `;
982
+
983
+ const emphasisedPrimaryButtonStyles = `
984
+ ${sharedButtonStyles}
985
+ color: ${componentButton['emphasised-primary'].shared.color};
986
+ background: ${componentButton['emphasised-primary'].shared.backgroundColor};
987
+ height: ${componentButton['emphasised-primary'].md.height};
988
+ padding: ${componentButton['emphasised-primary'].md.padding.top}
989
+ ${componentButton['emphasised-primary'].md.padding.right}
990
+ ${componentButton['emphasised-primary'].md.padding.bottom}
991
+ ${componentButton['emphasised-primary'].md.padding.left};
992
+ font: ${componentButton['emphasised-primary'].md.typography.font};
993
+ letter-spacing: ${componentButton['emphasised-primary'].md.typography.letterSpacing};
994
+ font-variation-settings: 'wdth'
995
+ ${componentButton['emphasised-primary'].md.typography.fontWidth};
996
+ border: ${componentButton['emphasised-primary'].shared.border};
997
+ border-radius: ${componentButton['emphasised-primary'].shared.borderRadius};
998
+ `;
999
+
1000
+ const emphasisedPrimaryButtonDisabledStyles = `
1001
+ ${emphasisedPrimaryButtonStyles}
1002
+ cursor: ${componentButton.shared[':disabled'].cursor};
1003
+ color: ${componentButton['emphasised-primary'].shared[':disabled'].color};
1004
+ background: ${componentButton['emphasised-primary'].shared[':disabled'].backgroundColor};
1005
+ border: ${componentButton['emphasised-primary'].shared[':disabled'].border};
1006
+ `;
1007
+
1008
+ const neutralSecondaryButtonStyles = `
1009
+ ${sharedButtonStyles}
1010
+ color: ${componentButton['neutral-secondary'].shared.color};
1011
+ background: ${componentButton['neutral-secondary'].shared.backgroundColor};
1012
+ height: ${componentButton['neutral-secondary'].md.height};
1013
+ padding: ${componentButton['neutral-secondary'].md.padding.top}
1014
+ ${componentButton['neutral-secondary'].md.padding.right}
1015
+ ${componentButton['neutral-secondary'].md.padding.bottom}
1016
+ ${componentButton['neutral-secondary'].md.padding.left};
1017
+ font: ${componentButton['neutral-secondary'].md.typography.font};
1018
+ letter-spacing: ${componentButton['neutral-secondary'].md.typography.letterSpacing};
1019
+ font-variation-settings: 'wdth'
1020
+ ${componentButton['neutral-secondary'].md.typography.fontWidth};
1021
+ border: ${componentButton['neutral-secondary'].shared.border};
1022
+ border-radius: ${componentButton['neutral-secondary'].shared.borderRadius};
1023
+ `;
1024
+
1025
+ // e.g. adding to DOM using vanilla JS styles
1026
+ document.getElementById('app')!.innerHTML = `
1027
+ <button style="${emphasisedPrimaryButtonStyles}">Button Label</button>
1028
+ <button disabled style="${emphasisedPrimaryButtonDisabledStyles}">Disabled Button Label</button>
1029
+ <a href="#" style="${neutralSecondaryButtonStyles}">LinkButton Label</a>
1030
+ `;
1031
+ ```
1032
+
1033
+ ### `Icon`
1034
+
1035
+ 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.
1036
+
1037
+ **When to use**
1038
+
1039
+ - Display icons alongside text or buttons
1040
+ - Represent actions, states, or categories visually
1041
+ - Provide visual cues in UI elements
1042
+
1043
+ **Peer dependencies:**
1044
+
1045
+ - `@emotion/react`
1046
+ - `react`
1047
+ - `react-dom`
1048
+ - `typescript`
1049
+
1050
+ See the `peerDependencies` section of `package.json` for compatible versions.
1051
+
1052
+ See [icon custom component build](#icon-custom-component-build) for usage without React/Emotion.
1053
+
1054
+ #### Example usage
1055
+
1056
+ See "Emotion/React" heading under the `Icon` component on [codesandbox.io](https://codesandbox.io/p/sandbox/mrzkrw).
1057
+
1058
+ ```tsx
1059
+ import { Icon } from '@guardian/stand/icon';
1060
+ import { baseColors } from '@guardian/stand';
1061
+
1062
+ /* types, if required */
1063
+ import type { IconProps, IconTheme } from '@guardian/stand/icon';
1064
+
1065
+ /* Material Symbols (font icon) */
1066
+ <Icon size="md" symbol="home"></Icon>
1067
+
1068
+ /* Material Symbols with custom color */
1069
+ <Icon size="md" fill={baseColors.red[500]} symbol="home"></Icon>
1070
+
1071
+ /* Standalone meaningful icon (use alt prop) */
1072
+ <Icon alt="Warning: High priority" symbol="warning"></Icon>
1073
+
1074
+ /* Material Icons (SVG) */
1075
+ import HomeIcon from '@material-design-icons/svg/outlined/home.svg?react';
1076
+ <Icon size="md" alt="Home">
1077
+ <HomeIcon />
1078
+ </Icon>
1079
+
1080
+ /* Custom SVG component */
1081
+ const CustomIcon = () => (
1082
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1083
+ <path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z" />
1084
+ </svg>
1085
+ );
1086
+
1087
+ <Icon size="lg" fill={baseColors.blue[500]} alt="Shield protection">
1088
+ <CustomIcon />
1089
+ </Icon>
1090
+ ```
1091
+
1092
+ #### Props
1093
+
1094
+ | Name | Type | Required | Default | Description |
1095
+ | -------------- | ------------------------------- | ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
1096
+ | `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. |
1097
+ | `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`. |
1098
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the icon dimensions. |
1099
+ | `fill` | `string` | No | N/A | Fill/color of the icon. Default is to inherit from text color or icon defaults. |
1100
+ | `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. |
1101
+ | `theme` | `IconTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
1102
+ | `cssOverrides` | `SerializedStyles` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
1103
+ | `className` | `string` | No | N/A | Additional class name(s) for the icon. For Material Symbols, combines with `material-symbols`. |
1104
+
1105
+ **`size`**
1106
+
1107
+ The icon supports three sizes:
1108
+
1109
+ - `sm` (small): 20px
1110
+ - `md` (medium): 24px
1111
+ - `lg` (large): 32px
1112
+
1113
+ **`symbol`**
1114
+
1115
+ 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.
1116
+
1117
+ **`children`**
1118
+
1119
+ The Icon component accepts two types of children:
1120
+
1121
+ - **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.)
1122
+ - **ReactNode** (SVG): Pass an SVG component (e.g., from `@material-design-icons/svg` or a custom SVG component).
1123
+
1124
+ **`alt`**
1125
+
1126
+ Provide alternative text to describe the icon's meaning for screen readers:
1127
+
1128
+ - **When to use**: Icons that convey meaning on their own (status indicators, standalone alerts, informational graphics)
1129
+ - **When to omit**: Icons that are purely decorative or appear alongside descriptive text
1130
+
1131
+ #### Customisation
1132
+
1133
+ We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
1134
+
1135
+ **Custom theme**
1136
+
1137
+ ```tsx
1138
+ import type { IconTheme } from '@guardian/stand/icon';
1139
+ import { Icon } from '@guardian/stand/icon';
1140
+ import { baseSizing } from '@guardian/stand';
1141
+
1142
+ const customTheme: Partial<IconTheme> = {
1143
+ shared: {
1144
+ display: 'block',
1145
+ 'user-select': 'all',
1146
+ },
1147
+ md: {
1148
+ size: baseSizing['size-48-rem'],
1149
+ },
1150
+ };
1151
+
1152
+ const Component = () => (
1153
+ <Icon size="md" theme={customTheme}>
1154
+ home
1155
+ </Icon>
1156
+ );
1157
+ ```
1158
+
1159
+ **CSS overrides**
1160
+
1161
+ ```tsx
1162
+ import { Icon } from '@guardian/stand/icon';
1163
+ import { baseColors, baseSpacing, semanticSizing } from '@guardian/stand';
1164
+ import { css } from '@emotion/react';
1165
+
1166
+ const customStyles = css`
1167
+ padding: ${baseSpacing['4-rem']};
1168
+ background-color: ${baseColors.yellow[400]};
1169
+ border-radius: ${semanticSizing.border['extra-wide']};
1170
+ `;
1171
+
1172
+ const Component = () => (
1173
+ <Icon size="lg" fill={baseColors.neutral[900]} cssOverrides={customStyles}>
1174
+ home
1175
+ </Icon>
1176
+ );
1177
+ ```
1178
+
1179
+ #### Icon Custom Component Build
1180
+
1181
+ 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.
1182
+
1183
+ You will however be responsible for any additional functionality on top of the styles, for example icon loading, accessibility, etc.
1184
+
1185
+ 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.
1186
+
1187
+ **`css`**
1188
+
1189
+ 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`:
1190
+
1191
+ ```css
1192
+ /* import the icon font and icon styles */
1193
+ @import '@guardian/stand/fonts/MaterialSymbolsOutlined.css';
1194
+ @import '@guardian/stand/component/icon.css';
1195
+
1196
+ /*
1197
+ or for scenarios where you have to use relative paths/node_modules directly:
1198
+
1199
+ @import 'node_modules/@guardian/stand/dist/fonts/MaterialSymbolsOutlined.css';
1200
+ @import 'node_modules/@guardian/stand/dist/style/build/css/component/icon.css';
1201
+ */
1202
+
1203
+ @import '@guardian/stand/base/spacing.css';
1204
+ @import '@guardian/stand/base/colors.css';
1205
+
1206
+ .stand-icon {
1207
+ display: var(--component-icon-shared-display);
1208
+ user-select: var(--component-icon-shared-user-select);
1209
+ font-size: var(--component-icon-lg-size);
1210
+ }
1211
+
1212
+ .stand-icon-font-color {
1213
+ color: var(--base-colors-magenta-400);
1214
+ }
1215
+
1216
+ .stand-icon-svg > svg {
1217
+ width: var(--component-icon-lg-size);
1218
+ height: var(--component-icon-lg-size);
1219
+ }
1220
+
1221
+ .stand-icon-svg-color > svg {
1222
+ fill: var(--base-colors-magenta-400);
1223
+ }
1224
+ ```
1225
+
1226
+ ```html
1227
+ <!-- Material Symbols (font icons) -->
1228
+ <span class="material-symbols stand-icon">home</span>
1229
+ <span class="material-symbols stand-icon stand-icon-font-color">upload</span>
1230
+
1231
+ <!-- SVG icons -->
1232
+ <span class="material-symbols stand-icon stand-icon-svg">
1233
+ <svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
1234
+ </span>
1235
+ <span class="material-symbols stand-icon stand-icon-svg stand-icon-svg-color">
1236
+ <svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
1237
+ </span>
1238
+ ```
1239
+
1240
+ **TypeScript/JavaScript**
1241
+
1242
+ Use the `componentIcon` variable and the `ComponentIcon` type to define your custom styles in TypeScript/JavaScript:
1243
+
1244
+ ```ts
1245
+ import type { ComponentIcon } from '@guardian/stand'; // if types required
1246
+ import { componentIcon, baseColors } from '@guardian/stand';
1247
+
1248
+ const iconStyles = `
1249
+ display: ${componentIcon.shared.display};
1250
+ user-select: ${componentIcon.shared['user-select']};
1251
+ font-size: ${componentIcon.lg.size};
1252
+ `;
1253
+
1254
+ const iconFontColorStyles = `
1255
+ ${iconStyles}
1256
+ color: ${baseColors.magenta[400]};
1257
+ `;
1258
+
1259
+ const iconSvgStyles = `
1260
+ width: ${componentIcon.lg.size};
1261
+ height: ${componentIcon.lg.size};
1262
+ `;
1263
+
1264
+ const iconSvgColorStyles = `
1265
+ ${iconSvgStyles}
1266
+ fill: ${baseColors.magenta[400]};
1267
+ `;
1268
+
1269
+ // e.g. adding to DOM using vanilla JS styles
1270
+ document.getElementById('app')!.innerHTML = `
1271
+ <h3>
1272
+ Using <code>typescript</code>/<code>javascript</code>
1273
+ </h3>
1274
+ <div>see <code>src/icon/custom.ts</code><div>
1275
+ <div style="margin-top: 4px;">Material Symbols</div>
1276
+ <div class="container">
1277
+ <span class="material-symbols" style="${iconStyles}">home</span>
1278
+ <span class="material-symbols" style="${iconFontColorStyles}">upload</span>
1279
+ </div>
1280
+ <div style="margin-top: 4px;">SVGs</div>
1281
+ <div class="container">
1282
+ <span class="material-symbols" style="${iconStyles}"><svg style="${iconSvgStyles}"...>...</svg></span>
1283
+ <span class="material-symbols" style="${iconFontColorStyles}"><svg style="${iconSvgColorStyles}"...>...</svg></span>
1284
+ </div>
1285
+ `;
1286
+ ```
1287
+
1288
+ ### `LinkButton`
1289
+
1290
+ 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`.
1291
+
1292
+ **When to use**
1293
+
1294
+ - Navigational links that should visually align with buttons
1295
+ - Cross-page CTAs where a button look-and-feel is preferred
1296
+ - Situations requiring disabled styling while preserving link semantics
1297
+
1298
+ **Peer dependencies:**
1299
+
1300
+ - `@emotion/react`
1301
+ - `react`
1302
+ - `react-dom`
1303
+ - `react-aria-components`
1304
+ - `typescript`
1305
+
1306
+ See the `peerDependencies` section of `package.json` for compatible versions.
1307
+
1308
+ See [link button custom component build](#button-custom-component-build) for usage without React/Emotion.
1309
+
1310
+ #### Example usage
1311
+
1312
+ See "Emotion/React" heading on [codesandbox.io](https://codesandbox.io/p/sandbox/jctg2k)
1313
+
1314
+ ```tsx
1315
+ import { LinkButton } from '@guardian/stand/button';
1316
+ import type { LinkButtonTheme, LinkButtonProps } from '@guardian/stand/button';
1317
+
1318
+ const Example = () => (
1319
+ <>
1320
+ <LinkButton
1321
+ href="/article"
1322
+ variant="emphasised-primary"
1323
+ size="md"
1324
+ target="_blank"
1325
+ rel="noreferrer"
1326
+ >
1327
+ Read article
1328
+ </LinkButton>
1329
+
1330
+ <LinkButton
1331
+ href="/settings"
1332
+ variant="neutral-secondary"
1333
+ size="sm"
1334
+ isDisabled
1335
+ >
1336
+ Disabled link
1337
+ </LinkButton>
1338
+ </>
1339
+ );
1340
+ ```
1341
+
1342
+ #### Props
1343
+
1344
+ | Name | Type | Required | Default | Description |
1345
+ | -------------- | -------------------------------------------------------------------------------------------------- | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
1346
+ | `href` | `string` | Yes | N/A | Destination URL for the link. |
1347
+ | `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` | No | `'md'` | Controls the link-button dimensions and typography. |
1348
+ | `variant` | `'emphasised-primary'` \| `'emphasised-secondary'` \| `'neutral-primary'` \| `'neutral-secondary'` | No | `'emphasised-primary'` | Chooses the colour scheme and interaction states. |
1349
+ | `children` | `ReactNode` | Yes | N/A | Content of the link. |
1350
+ | `isDisabled` | `boolean` | No | `false` | Disables interaction and applies disabled styling. |
1351
+ | `theme` | `LinkButtonTheme` (partial) | No | N/A | Override Stand tokens for this instance; merged over the default theme. |
1352
+ | `cssOverrides` | `SerializedStyles` \| `SerializedStyles[]` | No | N/A | Low-level escape hatch for Emotion overrides when theming is insufficient. |
1353
+ | `className` | `string` | No | N/A | Optional class name forwarded to the root React Aria link. |
1354
+ | `...props` | `ReactAria Link` props | No | N/A | All other props from `react-aria-components` `Link` (e.g. `target`, `rel`, `aria-label`, event handlers). |
1355
+
1356
+ **`size` and `variant`**
1357
+
1358
+ - Sizes: `xs`, `sm`, `md`, `lg`
1359
+ - Variants: `emphasised-primary`, `emphasised-secondary`, `neutral-primary`, `neutral-secondary`
1360
+
1361
+ #### Customisation
1362
+
1363
+ We recommend using the default theme. When needed, use the `theme` or `cssOverrides` props.
1364
+
1365
+ **Custom theme**
1366
+
1367
+ ```tsx
1368
+ import type { LinkButtonTheme } from '@guardian/stand/button';
1369
+ import { LinkButton } from '@guardian/stand/button';
1370
+ import { baseColors } from '@guardian/stand';
1371
+
1372
+ const customTheme: Partial<LinkButtonTheme> = {
1373
+ 'emphasised-primary': {
1374
+ shared: {
1375
+ backgroundColor: baseColors['cool-purple'][200],
1376
+ color: baseColors['cool-purple'][900],
1377
+ border: `2px solid ${baseColors['cool-purple'][700]}`,
1378
+ ':hover': {
1379
+ backgroundColor: baseColors['cool-purple'][300],
1380
+ border: `2px solid ${baseColors['cool-purple'][700]}`,
1381
+ },
1382
+ ':active': {
1383
+ backgroundColor: baseColors['cool-purple'][400],
1384
+ border: `2px solid ${baseColors['cool-purple'][700]}`,
1385
+ },
1386
+ },
1387
+ },
1388
+ };
1389
+
1390
+ const Component = () => (
1391
+ <LinkButton
1392
+ href="/"
1393
+ variant="emphasised-primary"
1394
+ size="md"
1395
+ theme={customTheme}
1396
+ >
1397
+ Custom Themed LinkButton
1398
+ </LinkButton>
1399
+ );
1400
+ ```
1401
+
1402
+ **CSS overrides**
1403
+
1404
+ ```tsx
1405
+ import { LinkButton } from '@guardian/stand/button';
1406
+ import { baseSpacing } from '@guardian/stand';
1407
+ import { css } from '@emotion/react';
1408
+
1409
+ const customStyles = css`
1410
+ width: 100%;
1411
+ text-transform: full-width;
1412
+ font-variant: small-caps;
1413
+ padding-inline: ${baseSpacing['24-rem']};
1414
+ `;
1415
+
1416
+ const Component = () => (
1417
+ <LinkButton
1418
+ href="/"
1419
+ variant="neutral-primary"
1420
+ size="sm"
1421
+ cssOverrides={customStyles}
1422
+ >
1423
+ CSSOverrides LinkButton
1424
+ </LinkButton>
1425
+ );
1426
+ ```
1427
+
1428
+ #### LinkButton Custom Component Build
1429
+
1430
+ 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.
1431
+
1432
+ ### Typography
1433
+
1434
+ The Typography component provides a convenient way to wrap React elements in a font variant.
1435
+
1436
+ **Peer dependencies**
1437
+
1438
+ - `@emotion/react`
1439
+ - `react`
1440
+ - `react-dom`
1441
+ - `typescript`
1442
+
1443
+ See the `peerDependencies` section of `package.json` for compatible versions.
1444
+
1445
+ #### Example usage
1446
+
1447
+ ```tsx
1448
+ import { Typography } from '@guardian/stand/typography';
1449
+
1450
+ /* types, if required */
1451
+ import type { Typography, TypographyTheme } from '@guardian/stand/typography';
1452
+
1453
+ /* Paragraph element with body-md preset and text children */
1454
+ <Typography element="p" variant="body-md">Body text here</Typography>
1455
+
1456
+ /* Div with an italic text wrapped inside */
1457
+ <Typography element="div" variant="body-md">Some text, with <Typography element="i" variant="body-italic-md">even more text</Typography></Typography>
1458
+ ```
1459
+
1460
+ #### Props
1461
+
1462
+ | Name | Type | Required | Default | Description |
1463
+ | -------------- | ------------------ | -------- | --------- | ---------------------------------------------------- |
1464
+ | `element` | Various | No | 'span' | HTML element to render with font applied to. |
1465
+ | `variant` | Various | No | 'body-md' | Font variant to apply as a CSS style to the element. |
1466
+ | `children` | `ReactNode` | No | N/A | Content to render inside the supplied HTML element. |
1467
+ | `theme` | `TypographyTheme` | No | N/A | Custom theme overrides for the typography. |
1468
+ | `cssOverrides` | `SerializedStyles` | No | N/A | Custom CSS styles for the typography. |
1469
+ | `className` | `string` | No | N/A | Additional class name(s) for the typography. |
1470
+
1471
+ #### Customisation
1472
+
1473
+ **Custom theme**
1474
+
1475
+ The `theme` prop allows you to override the color of the text:
1476
+
1477
+ ```tsx
1478
+ import type { TypographyTheme } from '@guardian/stand/typography';
1479
+ import { Typography } from '@guardian/stand/typography';
1480
+
1481
+ const customTheme: TypographyTheme = {
1482
+ color: 'red',
1483
+ };
1484
+ const Component = () => (
1485
+ <Typography element="p" variant="body-md" theme={customTheme}>
1486
+ Text
1487
+ </Typography>
1488
+ );
1489
+ ```
1490
+
1491
+ **CSS overrides**
1492
+
1493
+ The `cssOverrides` prop allows you to pass custom CSS to the rendered element.
1494
+
1495
+ ## Components - Editorial
1496
+
1497
+ Specialised components for use in specific editorial use cases.
458
1498
 
459
1499
  ### `Byline`
460
1500
 
@@ -464,6 +1504,7 @@ A flexible byline editor component built in ProseMirror and React with usability
464
1504
 
465
1505
  You'll need to install the following peer dependencies in your project to use the `Byline` component:
466
1506
 
1507
+ - `@emotion/react`
467
1508
  - `@guardian/prosemirror-invisibles`
468
1509
  - `prosemirror-dropcursor`
469
1510
  - `prosemirror-history`
@@ -471,14 +1512,19 @@ You'll need to install the following peer dependencies in your project to use th
471
1512
  - `prosemirror-model`
472
1513
  - `prosemirror-state`
473
1514
  - `prosemirror-view`
1515
+ - `react`
1516
+ - `react-dom`
1517
+ - `typescript`
1518
+
1519
+ See the `peerDependencies` section of `package.json` for compatible versions.
474
1520
 
475
- See the `peerDependencies` section of `package.json` for compatible versions to install.
1521
+ **Note:** If you only need the built CSS (`@guardian/stand/component/byline.css`), you don't need to install these dependencies.
476
1522
 
477
1523
  #### Usage
478
1524
 
479
1525
  ```tsx
480
- import type { BylineModel } from '@guardian/stand';
481
- import { Byline } from '@guardian/stand';
1526
+ import type { BylineModel } from '@guardian/stand/byline';
1527
+ import { Byline } from '@guardian/stand/byline';
482
1528
 
483
1529
  const Component = () => {
484
1530
  const bylineModel: BylineModel = {
@@ -518,9 +1564,15 @@ autocomplete input for selecting tags from a list of options, based on the [Reac
518
1564
 
519
1565
  **Peer dependencies:**
520
1566
 
1567
+ - `@emotion/react`
1568
+ - `react`
521
1569
  - `react-aria-components`
1570
+ - `react-dom`
1571
+ - `typescript`
522
1572
 
523
- See the `peerDependencies` section of the `package.json` for compatible versions to install.
1573
+ See the `peerDependencies` section of `package.json` for compatible versions.
1574
+
1575
+ **Note:** If you only need the built CSS (`@guardian/stand/component/tagAutocomplete.css`), you don't need to install these dependencies.
524
1576
 
525
1577
  ##### Props
526
1578
 
@@ -536,9 +1588,15 @@ based on the [React Aria Table](https://react-spectrum.adobe.com/react-aria/Tabl
536
1588
 
537
1589
  **Peer dependencies:**
538
1590
 
1591
+ - `@emotion/react`
1592
+ - `react`
539
1593
  - `react-aria-components`
1594
+ - `react-dom`
1595
+ - `typescript`
1596
+
1597
+ See the `peerDependencies` section of `package.json` for compatible versions.
540
1598
 
541
- See the `peerDependencies` section of the `package.json` for compatible versions to install.
1599
+ **Note:** If you only need the built CSS (`@guardian/stand/component/tagTable.css`), you don't need to install these dependencies.
542
1600
 
543
1601
  ##### Props
544
1602
 
@@ -549,7 +1607,7 @@ See [`TagTableProps`](src/components/tag-picker/TagTable.tsx#L31) for the full l
549
1607
  _Example with TagAutocomplete and TagTable combined:_
550
1608
 
551
1609
  ```tsx
552
- import { TagAutocomplete, TagTable } from '@guardian/stand';
1610
+ import { TagAutocomplete, TagTable } from '@guardian/stand/tag-picker';
553
1611
 
554
1612
  const Component = () => {
555
1613
  const [selectedTags, setSelectedTags] = useState<
@@ -610,6 +1668,86 @@ const Component = () => {
610
1668
 
611
1669
  This is currently still in testing phase, so a production implementation is not yet available.
612
1670
 
1671
+ ### `UserMenu`
1672
+
1673
+ The UserMenu component presents a collection of accessibility settings for users to customise their experience of using the application. The current supported settings are:
1674
+
1675
+ - "Text Size"
1676
+ - "Font Family"
1677
+ - "Color scheme"
1678
+
1679
+ **Peer dependencies:**
1680
+
1681
+ - `@emotion/react`
1682
+ - `react`
1683
+ - `react-dom`
1684
+ - `typescript`
1685
+ - `react-aria-components`
1686
+
1687
+ See the `peerDependencies` section of `package.json` for compatible versions.
1688
+
1689
+ **When to use**
1690
+
1691
+ - as an application-level "accessibility options" panel
1692
+ - as part of a more general options page
1693
+
1694
+ #### Usage
1695
+
1696
+ ```tsx
1697
+ import { UserMenu, type UserMenuProps } from '@guardian/stand/user-menu';
1698
+
1699
+ const customFontFamilyOptions = [
1700
+ {
1701
+ id: 'white',
1702
+ buttonStyle: {
1703
+ backgroundColor: 'white',
1704
+ },
1705
+ isDefault: true,
1706
+ },
1707
+ {
1708
+ id: 'pink',
1709
+ buttonStyle: {
1710
+ backgroundColor: 'pink',
1711
+ },
1712
+ },
1713
+ ];
1714
+
1715
+ const Component = ({
1716
+ currentPreferences,
1717
+ updatePreferences,
1718
+ }: {
1719
+ currentPreferences: UserMenuProps['preferences'];
1720
+ updatePreferences: UserMenuProps['updatePreferences'];
1721
+ }) => {
1722
+ ...
1723
+ return (
1724
+ <>
1725
+ ...
1726
+ <UserMenu
1727
+ feedBacklink="https://example.com/feedback-form"
1728
+ fontFamilyOptions={customFontFamilyOptions}
1729
+ colorSchemeOptions={[]}
1730
+ preferences={currentPreferences}
1731
+ updatePreferences={updatePreferences}
1732
+ />
1733
+ ...
1734
+ </>
1735
+ );
1736
+ };
1737
+ ```
1738
+
1739
+ `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.
1740
+
1741
+ 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).
1742
+
1743
+ #### Props
1744
+
1745
+ See [`UserMenuProps`](src/user-menu/UserMenu.tsx#L14) for the full list of props, usage example can be seen in Storybook.
1746
+
1747
+ #### Example
1748
+
1749
+ This is currently still in testing phase, so a production implementation is not yet available.
1750
+
613
1751
  ### Contributing
614
1752
 
615
1753
  See the [Contributing to Stand](./CONTRIBUTING.md) documentation for guidelines on contributing to this project. Project setup and common tasks are listed below.