@react-ui-org/react-ui 0.51.0 → 0.52.0

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 (39) hide show
  1. package/dist/lib.development.js +140 -32
  2. package/dist/lib.js +1 -1
  3. package/package.json +1 -1
  4. package/src/lib/components/Button/Button.jsx +17 -9
  5. package/src/lib/components/Button/_base.scss +21 -12
  6. package/src/lib/components/Button/_priorities.scss +1 -18
  7. package/src/lib/components/Button/_theme.scss +0 -10
  8. package/src/lib/components/ButtonGroup/ButtonGroup.jsx +5 -3
  9. package/src/lib/components/ButtonGroup/ButtonGroup.scss +26 -1
  10. package/src/lib/components/ButtonGroup/README.mdx +11 -1
  11. package/src/lib/components/ButtonGroup/_theme.scss +13 -0
  12. package/src/lib/components/FormLayout/README.mdx +5 -0
  13. package/src/lib/components/InputGroup/InputGroup.jsx +170 -0
  14. package/src/lib/components/InputGroup/InputGroup.scss +92 -0
  15. package/src/lib/components/InputGroup/InputGroupContext.js +3 -0
  16. package/src/lib/components/InputGroup/README.mdx +278 -0
  17. package/src/lib/components/InputGroup/_theme.scss +2 -0
  18. package/src/lib/components/InputGroup/index.js +2 -0
  19. package/src/lib/components/Modal/Modal.jsx +58 -97
  20. package/src/lib/components/Modal/README.mdx +288 -15
  21. package/src/lib/components/Modal/_helpers/getPositionClassName.js +7 -0
  22. package/src/lib/components/Modal/_helpers/getSizeClassName.js +19 -0
  23. package/src/lib/components/Modal/_hooks/useModalFocus.js +126 -0
  24. package/src/lib/components/Modal/_hooks/useModalScrollPrevention.js +35 -0
  25. package/src/lib/components/Modal/_settings.scss +1 -1
  26. package/src/lib/components/Radio/README.mdx +9 -1
  27. package/src/lib/components/Radio/Radio.jsx +39 -31
  28. package/src/lib/components/Radio/Radio.scss +11 -1
  29. package/src/lib/components/SelectField/SelectField.jsx +21 -8
  30. package/src/lib/components/SelectField/SelectField.scss +5 -0
  31. package/src/lib/components/TextField/TextField.jsx +21 -8
  32. package/src/lib/components/TextField/TextField.scss +5 -0
  33. package/src/lib/index.js +1 -0
  34. package/src/lib/styles/theme/_borders.scss +2 -1
  35. package/src/lib/styles/tools/form-fields/_box-field-elements.scss +19 -2
  36. package/src/lib/styles/tools/form-fields/_box-field-sizes.scss +11 -8
  37. package/src/lib/styles/tools/form-fields/_foundation.scss +7 -0
  38. package/src/lib/theme.scss +23 -11
  39. /package/src/lib/components/{Button/helpers → _helpers}/getRootPriorityClassName.js +0 -0
@@ -25,6 +25,7 @@ import {
25
25
  ModalTitle,
26
26
  Radio,
27
27
  ScrollView,
28
+ TextArea,
28
29
  TextField,
29
30
  Toolbar,
30
31
  ToolbarGroup,
@@ -116,12 +117,15 @@ See [API](#api) for all available options.
116
117
 
117
118
  - Modal **automatically focuses the first non-disabled form field** by default
118
119
  which allows users to confirm the modal by hitting the enter key. When no
119
- field is found then the primary button (in the footer) is focused. To turn
120
- this feature off, set the `autofocus` prop to `false`.
120
+ field is found then the primary button (in the footer) is focused. If there
121
+ are neither, it tries to focus any other focusable elements. In case there
122
+ are none, or [autoFocus](#autoFocus) is disabled, Modal itself is focused.
121
123
 
122
124
  - **Avoid stacking** of modals. While it may technically work, the modal is just
123
125
  not designed for that.
124
126
 
127
+ 📖 [Read more about modals at Nielsen Norman Group.][nng-modal]
128
+
125
129
  ## Composition
126
130
 
127
131
  Modal is decomposed into the following components:
@@ -822,24 +826,147 @@ can be closed by pressing the `Escape` key.
822
826
 
823
827
  To enable it, you just need to pass a reference to the buttons using
824
828
  `primaryButtonRef` and `closeButtonRef` props on Modal. The advantage of passing
825
- a reference to the button is that if the button is disabled, the key press will
826
- not fire the event.
827
-
828
- 👉 We strongly recommend using this feature together with
829
- [Autofocus](#autofocus) for a better user experience.
829
+ the reference to the button is that if the button is disabled, the key press
830
+ will not fire the event.
830
831
 
831
832
  ## Autofocus
832
833
 
833
834
  Autofocus is implemented to enhance the user experience by automatically
834
- focussing an element within the modal.
835
+ focusing an element within the Modal.
835
836
 
836
837
  How does it work? It tries to find `input`, `textarea`, and `select` elements
837
838
  inside of Modal and moves focus onto the first non-disabled one. If none is
838
839
  found and the `primaryButtonRef` prop on Modal is set, then the primary button
839
- is focused.
840
+ is focused. If there are neither, it tries to focus any other focusable elements.
841
+ In case there are none or `autoFocus` is disabled, Modal itself is focused.
840
842
 
841
- Autofocus is enabled by default, so if you want to control the focus of
842
- elements manually, set the `autoFocus` prop on Modal to `false`.
843
+ <Playground>
844
+ {() => {
845
+ const [modalOpen, setModalOpen] = React.useState(null);
846
+ const modalPrimaryButtonRef = React.useRef();
847
+ const modalCloseButtonRef = React.useRef();
848
+ return (
849
+ <>
850
+ <Button
851
+ label="Launch modal with autofocus and form"
852
+ onClick={() => setModalOpen(1)}
853
+ />
854
+ <Button
855
+ label="Launch modal with autofocus"
856
+ onClick={() => setModalOpen(2)}
857
+ />
858
+ <Button
859
+ label="Launch modal with autofocus disabled"
860
+ onClick={() => setModalOpen(3)}
861
+ />
862
+ <div>
863
+ {modalOpen === 1 && (
864
+ <Modal
865
+ closeButtonRef={modalCloseButtonRef}
866
+ primaryButtonRef={modalPrimaryButtonRef}
867
+ >
868
+ <ModalHeader>
869
+ <ModalTitle>Modal with autoFocus and form</ModalTitle>
870
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
871
+ </ModalHeader>
872
+ <ModalBody>
873
+ <ModalContent>
874
+ <FormLayout autoWidth fieldLayout="horizontal">
875
+ <TextField
876
+ disabled
877
+ label="A form element"
878
+ />
879
+ <TextField label="Another form element" />
880
+ <TextArea label="Yet another one" />
881
+ </FormLayout>
882
+ </ModalContent>
883
+ </ModalBody>
884
+ <ModalFooter>
885
+ <Button
886
+ label="Submit"
887
+ onClick={() => setModalOpen(null)}
888
+ ref={modalPrimaryButtonRef}
889
+ />
890
+ <Button
891
+ color="secondary"
892
+ label="Close"
893
+ onClick={() => setModalOpen(null)}
894
+ priority="outline"
895
+ ref={modalCloseButtonRef}
896
+ />
897
+ </ModalFooter>
898
+ </Modal>
899
+ )}
900
+ {modalOpen === 2 && (
901
+ <Modal
902
+ closeButtonRef={modalCloseButtonRef}
903
+ primaryButtonRef={modalPrimaryButtonRef}
904
+ >
905
+ <ModalHeader>
906
+ <ModalTitle>Modal with autoFocus enabled with no form</ModalTitle>
907
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
908
+ </ModalHeader>
909
+ <ModalBody>
910
+ <ModalContent>
911
+ <p>
912
+ This Modal autofocuses the primary button or any other
913
+ focusable element.
914
+ </p>
915
+ </ModalContent>
916
+ </ModalBody>
917
+ <ModalFooter>
918
+ <Button
919
+ label="Acknowledge"
920
+ onClick={() => setModalOpen(null)}
921
+ ref={modalPrimaryButtonRef}
922
+ />
923
+ <Button
924
+ color="secondary"
925
+ label="Close"
926
+ onClick={() => setModalOpen(null)}
927
+ priority="outline"
928
+ ref={modalCloseButtonRef}
929
+ />
930
+ </ModalFooter>
931
+ </Modal>
932
+ )}
933
+ {modalOpen === 3 && (
934
+ <Modal
935
+ autoFocus={false}
936
+ closeButtonRef={modalCloseButtonRef}
937
+ primaryButtonRef={modalPrimaryButtonRef}
938
+ >
939
+ <ModalHeader>
940
+ <ModalTitle>Modal with autoFocus disabled</ModalTitle>
941
+ </ModalHeader>
942
+ <ModalBody>
943
+ <ModalContent>
944
+ <p>
945
+ This Modal focuses the Modal element itself.
946
+ </p>
947
+ </ModalContent>
948
+ </ModalBody>
949
+ <ModalFooter>
950
+ <Button
951
+ label="Acknowledge"
952
+ onClick={() => setModalOpen(null)}
953
+ ref={modalPrimaryButtonRef}
954
+ />
955
+ <Button
956
+ color="secondary"
957
+ label="Close"
958
+ onClick={() => setModalOpen(null)}
959
+ priority="outline"
960
+ ref={modalCloseButtonRef}
961
+ />
962
+ </ModalFooter>
963
+ </Modal>
964
+ )}
965
+ </div>
966
+ </>
967
+ );
968
+ }}
969
+ </Playground>
843
970
 
844
971
  ## Scrolling Long Content
845
972
 
@@ -959,7 +1086,7 @@ independent of the page itself. This can be done in three ways using the
959
1086
  <div>
960
1087
  {modalOpen && (
961
1088
  <Modal
962
- autoFocus={false}
1089
+ autoFocus={modalScrolling !== 'none'}
963
1090
  closeButtonRef={modalCloseButtonRef}
964
1091
  primaryButtonRef={modalPrimaryButtonRef}
965
1092
  size="small"
@@ -1003,9 +1130,154 @@ independent of the page itself. This can be done in three ways using the
1003
1130
 
1004
1131
  ### Long Content and Autofocus
1005
1132
 
1006
- 👉 If you wrap ModalContent with ScrollView, you may want to turn `autoFocus`
1007
- off to prevent the modal from scrolling to the end immediately after being
1008
- opened.
1133
+ 👉 If you have a Modal with `scrolling` set to `none`, you may want to disable
1134
+ `autoFocus` to prevent the modal from scrolling to the end immediately after
1135
+ being opened.
1136
+
1137
+ ## Prevent Scrolling Underneath the Modal
1138
+
1139
+ You can choose the mode in which Modal prevents the scroll of the page underneath.
1140
+ Default mode prevents scrolling on `<body>` element and accounts for the scrollbar
1141
+ width. If you choose `off`, there will be no scroll prevention. If you need more
1142
+ flexibility, define your methods `start` (called on Modal's mount) and `reset`
1143
+ (called on Modal unmount) wrapped by an object and handle scroll prevention
1144
+ yourself.
1145
+
1146
+ <Playground>
1147
+ {() => {
1148
+ const [modalOpen, setModalOpen] = React.useState(null);
1149
+ const modalPrimaryButtonRef = React.useRef();
1150
+ const modalCloseButtonRef = React.useRef();
1151
+ const customScrollPreventionObject = {
1152
+ start: () => {
1153
+ // YOUR CUSTOM SCROLL PREVENTING LOGIC GOES HERE
1154
+ window.document.body.style.overflowY = 'hidden'
1155
+ },
1156
+ reset: () => {
1157
+ // YOUR CUSTOM SCROLL RE-ENABLING LOGIC GOES HERE
1158
+ window.document.body.style.overflowY = 'auto'
1159
+ },
1160
+ };
1161
+ return (
1162
+ <>
1163
+ <Button
1164
+ label="Launch modal with default scroll prevention"
1165
+ onClick={() => setModalOpen(1)}
1166
+ />
1167
+ <Button
1168
+ label="Launch modal with no scroll prevention"
1169
+ onClick={() => setModalOpen(2)}
1170
+ />
1171
+ <Button
1172
+ label="Launch modal with custom scroll prevention"
1173
+ onClick={() => setModalOpen(3)}
1174
+ />
1175
+ <div>
1176
+ {modalOpen === 1 && (
1177
+ <Modal
1178
+ closeButtonRef={modalCloseButtonRef}
1179
+ primaryButtonRef={modalPrimaryButtonRef}
1180
+ >
1181
+ <ModalHeader>
1182
+ <ModalTitle>Modal with default scroll prevention</ModalTitle>
1183
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
1184
+ </ModalHeader>
1185
+ <ModalBody>
1186
+ <ModalContent>
1187
+ <p>
1188
+ This Modal uses default scroll prevention on the document's
1189
+ <code>body</code> element.
1190
+ </p>
1191
+ </ModalContent>
1192
+ </ModalBody>
1193
+ <ModalFooter>
1194
+ <Button
1195
+ label="Acknowledge"
1196
+ onClick={() => setModalOpen(null)}
1197
+ ref={modalPrimaryButtonRef}
1198
+ />
1199
+ <Button
1200
+ color="secondary"
1201
+ label="Close"
1202
+ onClick={() => setModalOpen(null)}
1203
+ priority="outline"
1204
+ ref={modalCloseButtonRef}
1205
+ />
1206
+ </ModalFooter>
1207
+ </Modal>
1208
+ )}
1209
+ {modalOpen === 2 && (
1210
+ <Modal
1211
+ closeButtonRef={modalCloseButtonRef}
1212
+ preventScrollUnderneath="off"
1213
+ primaryButtonRef={modalPrimaryButtonRef}
1214
+ >
1215
+ <ModalHeader>
1216
+ <ModalTitle>Modal with no scroll prevention</ModalTitle>
1217
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
1218
+ </ModalHeader>
1219
+ <ModalBody>
1220
+ <ModalContent>
1221
+ <p>
1222
+ This Modal does not prevent scrolling.
1223
+ </p>
1224
+ </ModalContent>
1225
+ </ModalBody>
1226
+ <ModalFooter>
1227
+ <Button
1228
+ label="Acknowledge"
1229
+ onClick={() => setModalOpen(null)}
1230
+ ref={modalPrimaryButtonRef}
1231
+ />
1232
+ <Button
1233
+ color="secondary"
1234
+ label="Close"
1235
+ onClick={() => setModalOpen(null)}
1236
+ priority="outline"
1237
+ ref={modalCloseButtonRef}
1238
+ />
1239
+ </ModalFooter>
1240
+ </Modal>
1241
+ )}
1242
+ {modalOpen === 3 && (
1243
+ <Modal
1244
+ closeButtonRef={modalCloseButtonRef}
1245
+ preventScrollUnderneath={customScrollPreventionObject}
1246
+ primaryButtonRef={modalPrimaryButtonRef}
1247
+ >
1248
+ <ModalHeader>
1249
+ <ModalTitle>Modal with custom scroll prevention</ModalTitle>
1250
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
1251
+ </ModalHeader>
1252
+ <ModalBody>
1253
+ <ModalContent>
1254
+ <p>
1255
+ This Modal uses provided custom functions to prevent scrolling
1256
+ and reset it on unmount.
1257
+ </p>
1258
+ </ModalContent>
1259
+ </ModalBody>
1260
+ <ModalFooter>
1261
+ <Button
1262
+ label="Acknowledge"
1263
+ onClick={() => setModalOpen(null)}
1264
+ ref={modalPrimaryButtonRef}
1265
+ />
1266
+ <Button
1267
+ color="secondary"
1268
+ label="Close"
1269
+ onClick={() => setModalOpen(null)}
1270
+ priority="outline"
1271
+ ref={modalCloseButtonRef}
1272
+ />
1273
+ </ModalFooter>
1274
+ </Modal>
1275
+ )}
1276
+ </div>
1277
+ </>
1278
+ );
1279
+ }}
1280
+ </Playground>
1009
1281
 
1010
1282
  <!-- markdownlint-disable MD024 -->
1011
1283
 
@@ -1081,6 +1353,7 @@ accessibility.
1081
1353
  | `--rui-Modal--fullscreen__width` | Width of fullscreen modal |
1082
1354
  | `--rui-Modal--fullscreen__height` | Height of fullscreen modal |
1083
1355
 
1356
+ [nng-modal]: https://www.nngroup.com/articles/modal-nonmodal-dialog/
1084
1357
  [React synthetic events]: https://reactjs.org/docs/events.html
1085
1358
  [div]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes
1086
1359
  [heading]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes
@@ -0,0 +1,7 @@
1
+ export const getPositionClassName = (modalPosition, styles) => {
2
+ if (modalPosition === 'top') {
3
+ return styles.isRootPositionTop;
4
+ }
5
+
6
+ return styles.isRootPositionCenter;
7
+ };
@@ -0,0 +1,19 @@
1
+ export const getSizeClassName = (modalSize, styles) => {
2
+ if (modalSize === 'small') {
3
+ return styles.isRootSizeSmall;
4
+ }
5
+
6
+ if (modalSize === 'medium') {
7
+ return styles.isRootSizeMedium;
8
+ }
9
+
10
+ if (modalSize === 'large') {
11
+ return styles.isRootSizeLarge;
12
+ }
13
+
14
+ if (modalSize === 'fullscreen') {
15
+ return styles.isRootSizeFullscreen;
16
+ }
17
+
18
+ return styles.isRootSizeAuto;
19
+ };
@@ -0,0 +1,126 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export const useModalFocus = (
4
+ autoFocus,
5
+ childrenWrapperRef,
6
+ primaryButtonRef,
7
+ closeButtonRef,
8
+ ) => {
9
+ useEffect(
10
+ () => {
11
+ // Following code finds all focusable elements and among them first not disabled form
12
+ // field element (input, textarea or select) or primary button and focuses it. This is
13
+ // necessary to have focus on one of those elements to be able to submit the form
14
+ // by pressing Enter key. If there are neither, it tries to focus any other focusable
15
+ // elements. In case there are none or `autoFocus` is disabled, childrenWrapperElement
16
+ // (Modal itself) is focused.
17
+
18
+ const childrenWrapperElement = childrenWrapperRef.current;
19
+
20
+ if (childrenWrapperElement == null) {
21
+ return () => {};
22
+ }
23
+
24
+ const childrenFocusableElements = Array.from(
25
+ childrenWrapperElement.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
26
+ );
27
+
28
+ const firstFocusableElement = childrenFocusableElements[0];
29
+ const lastFocusableElement = childrenFocusableElements[childrenFocusableElements.length - 1];
30
+
31
+ const resolveFocusBeforeListener = () => {
32
+ if (!autoFocus || childrenFocusableElements.length === 0) {
33
+ childrenWrapperElement.tabIndex = -1;
34
+ childrenWrapperElement.focus();
35
+ return;
36
+ }
37
+
38
+ const firstFormFieldEl = childrenFocusableElements.find(
39
+ (element) => ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && !element.disabled,
40
+ );
41
+
42
+ if (firstFormFieldEl) {
43
+ firstFormFieldEl.focus();
44
+ return;
45
+ }
46
+
47
+ if (primaryButtonRef?.current != null) {
48
+ primaryButtonRef.current.focus();
49
+ return;
50
+ }
51
+
52
+ firstFocusableElement.focus();
53
+ };
54
+
55
+ const keyPressHandler = (e) => {
56
+ if (e.key === 'Escape' && closeButtonRef?.current != null) {
57
+ closeButtonRef.current.click();
58
+ return;
59
+ }
60
+
61
+ if (
62
+ e.key === 'Enter'
63
+ && e.target.nodeName !== 'BUTTON'
64
+ && e.target.nodeName !== 'TEXTAREA'
65
+ && e.target.nodeName !== 'A'
66
+ && primaryButtonRef?.current != null
67
+ ) {
68
+ primaryButtonRef.current.click();
69
+ return;
70
+ }
71
+
72
+ // Following code traps focus inside Modal
73
+
74
+ if (e.key !== 'Tab') {
75
+ return;
76
+ }
77
+
78
+ if (childrenFocusableElements.length === 0) {
79
+ childrenWrapperElement.focus();
80
+ e.preventDefault();
81
+ return;
82
+ }
83
+
84
+ if (
85
+ ![
86
+ ...childrenFocusableElements,
87
+ childrenWrapperElement,
88
+ ]
89
+ .includes(window.document.activeElement)
90
+ ) {
91
+ firstFocusableElement.focus();
92
+ e.preventDefault();
93
+ return;
94
+ }
95
+
96
+ if (!e.shiftKey && window.document.activeElement === lastFocusableElement) {
97
+ firstFocusableElement.focus();
98
+ e.preventDefault();
99
+ return;
100
+ }
101
+
102
+ if (e.shiftKey
103
+ && (
104
+ window.document.activeElement === firstFocusableElement
105
+ || window.document.activeElement === childrenWrapperElement
106
+ )
107
+ ) {
108
+ lastFocusableElement.focus();
109
+ e.preventDefault();
110
+ }
111
+ };
112
+
113
+ resolveFocusBeforeListener();
114
+
115
+ window.document.addEventListener('keydown', keyPressHandler, false);
116
+
117
+ return () => window.document.removeEventListener('keydown', keyPressHandler, false);
118
+ },
119
+ [
120
+ autoFocus,
121
+ childrenWrapperRef,
122
+ primaryButtonRef,
123
+ closeButtonRef,
124
+ ],
125
+ );
126
+ };
@@ -0,0 +1,35 @@
1
+ import { useLayoutEffect } from 'react';
2
+
3
+ export const useModalScrollPrevention = (preventScrollUnderneath) => {
4
+ useLayoutEffect(
5
+ () => {
6
+ if (preventScrollUnderneath === 'off') {
7
+ return () => {};
8
+ }
9
+
10
+ if (preventScrollUnderneath === 'default') {
11
+ const scrollbarWidth = Math.abs(window.innerWidth - window.document.documentElement.clientWidth);
12
+ const prevOverflow = window.document.body.style.overflow;
13
+ const prevPaddingRight = window.document.body.style.paddingRight;
14
+
15
+ window.document.body.style.overflow = 'hidden';
16
+
17
+ if (Number.isNaN(parseInt(prevPaddingRight, 10))) {
18
+ window.document.body.style.paddingRight = `${scrollbarWidth}px`;
19
+ } else {
20
+ window.document.body.style.paddingRight = `calc(${prevPaddingRight} + ${scrollbarWidth}px)`;
21
+ }
22
+
23
+ return () => {
24
+ window.document.body.style.overflow = prevOverflow;
25
+ window.document.body.style.paddingRight = prevPaddingRight;
26
+ };
27
+ }
28
+
29
+ preventScrollUnderneath?.start();
30
+
31
+ return preventScrollUnderneath?.reset;
32
+ },
33
+ [preventScrollUnderneath],
34
+ );
35
+ };
@@ -3,7 +3,7 @@
3
3
  @use "../../styles/theme/borders";
4
4
  @use "../../styles/theme/typography";
5
5
 
6
- $border-radius: borders.$radius;
6
+ $border-radius: borders.$radius-2;
7
7
  $z-index: z-indexes.$modal;
8
8
  $backdrop-z-index: z-indexes.$modal-backdrop;
9
9
  $title-font-size: map.get(typography.$font-size-values, 2);
@@ -77,7 +77,12 @@ See [API](#api) for all available options.
77
77
  - Use **clear, calm error messages** when there's a problem with what they
78
78
  entered.
79
79
 
80
- 📖 [Read more about checkboxes and radios at Nielsen Norman Group.](https://www.nngroup.com/articles/checkboxes-vs-radio-buttons/)
80
+ - In the background, Radio uses the [`fieldset`][fieldset] element. Not only it
81
+ improves the [accessibility] of the group, it also allows you to make use of
82
+ its built-in features like disabling all nested inputs or pairing the group
83
+ with a form outside. Consult [the MDN docs][fieldset] to learn more.
84
+
85
+ 📖 [Read more about checkboxes and radios at Nielsen Norman Group.][nng-radio]
81
86
 
82
87
  ## Invisible Label
83
88
 
@@ -308,5 +313,8 @@ options. On top of that, the following options are available for Radio.
308
313
  | `--rui-FormField--check__input--radio__border-radius` | Input corner radius |
309
314
  | `--rui-FormField--check__input--radio--checked__background-image` | Checked input background image (inline, URL, …) |
310
315
 
316
+ [nng-radio]: https://www.nngroup.com/articles/checkboxes-vs-radio-buttons/
317
+ [fieldset]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset
318
+ [accessibility]: https://www.w3.org/WAI/tutorials/forms/grouping/
311
319
  [React synthetic events]: https://reactjs.org/docs/events.html
312
320
  [radio]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#additional_attributes