|
| 1 | +{/* Copyright 2020 Adobe. All rights reserved. |
| 2 | +This file is licensed to you under the Apache License, Version 2.0 (the "License"); |
| 3 | +you may not use this file except in compliance with the License. You may obtain a copy |
| 4 | +of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| 5 | +Unless required by applicable law or agreed to in writing, software distributed under |
| 6 | +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS |
| 7 | +OF ANY KIND, either express or implied. See the License for the specific language |
| 8 | +governing permissions and limitations under the License. */} |
| 9 | + |
| 10 | +import {Layout} from '@react-spectrum/docs'; |
| 11 | +export default Layout; |
| 12 | + |
| 13 | +import docs from 'docs:react-aria-components'; |
| 14 | +import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable} from '@react-spectrum/docs'; |
| 15 | +import styles from '@react-spectrum/docs/src/docs.css'; |
| 16 | +import packageData from 'react-aria-components/package.json'; |
| 17 | +import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; |
| 18 | +import {Divider} from '@react-spectrum/divider'; |
| 19 | +import {Keyboard} from '@react-spectrum/text'; |
| 20 | + |
| 21 | +--- |
| 22 | +category: Content |
| 23 | +keywords: [group, aria] |
| 24 | +type: component |
| 25 | +--- |
| 26 | + |
| 27 | +# Group |
| 28 | + |
| 29 | +<PageDescription>{docs.exports.Group.description}</PageDescription> |
| 30 | + |
| 31 | +<HeaderInfo |
| 32 | + packageData={packageData} |
| 33 | + componentNames={['Group']} |
| 34 | + sourceData={[ |
| 35 | + {type: 'W3C', url: 'https://w3c.github.io/aria/#group'} |
| 36 | + ]} /> |
| 37 | + |
| 38 | +## Example |
| 39 | + |
| 40 | +```tsx example |
| 41 | +import {TextField, Label, Group, Input, Button} from 'react-aria-components'; |
| 42 | + |
| 43 | +<TextField> |
| 44 | + <Label>Email</Label> |
| 45 | + <Group> |
| 46 | + <Input /> |
| 47 | + <Button aria-label="Add email">➕</Button> |
| 48 | + </Group> |
| 49 | +</TextField> |
| 50 | +``` |
| 51 | + |
| 52 | +<details> |
| 53 | + <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary> |
| 54 | + |
| 55 | +```css |
| 56 | +.react-aria-Group { |
| 57 | + --field-border: var(--spectrum-alias-border-color); |
| 58 | + --field-border-hovered: var(--spectrum-alias-border-color-hover); |
| 59 | + --field-background: var(--spectrum-global-color-gray-50); |
| 60 | + --text-color: var(--spectrum-alias-text-color); |
| 61 | + --text-color-disabled: var(--spectrum-alias-text-color-disabled); |
| 62 | + --focus-ring-color: slateblue; |
| 63 | + --invalid-color: var(--spectrum-global-color-red-600); |
| 64 | + |
| 65 | + display: flex; |
| 66 | + align-items: center; |
| 67 | + width: fit-content; |
| 68 | + border-radius: 6px; |
| 69 | + border: 1px solid var(--field-border); |
| 70 | + background: var(--field-background); |
| 71 | + overflow: hidden; |
| 72 | + |
| 73 | + &[data-hovered] { |
| 74 | + border-color: var(--field-border-hovered); |
| 75 | + } |
| 76 | + |
| 77 | + &[data-focus-within] { |
| 78 | + border-color: var(--focus-ring-color); |
| 79 | + box-shadow: 0 0 0 1px var(--focus-ring-color); |
| 80 | + } |
| 81 | + |
| 82 | + .react-aria-Input { |
| 83 | + padding: 0.286rem; |
| 84 | + margin: 0; |
| 85 | + font-size: 1rem; |
| 86 | + color: var(--text-color); |
| 87 | + outline: none; |
| 88 | + border: none; |
| 89 | + background: transparent; |
| 90 | + |
| 91 | + &::placeholder { |
| 92 | + color: var(--spectrum-gray-600); |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + .react-aria-Button { |
| 97 | + padding: 0 6px; |
| 98 | + border-width: 0 0 0 1px; |
| 99 | + border-radius: 0 6px 6px 0; |
| 100 | + align-self: stretch; |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +.react-aria-Button { |
| 105 | + --border-color: var(--spectrum-alias-border-color); |
| 106 | + --border-color-pressed: var(--spectrum-alias-border-color-down); |
| 107 | + --border-color-disabled: var(--spectrum-alias-border-color-disabled); |
| 108 | + --background-color: var(--spectrum-global-color-gray-50); |
| 109 | + --background-color-pressed: var(--spectrum-global-color-gray-100); |
| 110 | + --text-color: var(--spectrum-alias-text-color); |
| 111 | + --text-color-disabled: var(--spectrum-alias-text-color-disabled); |
| 112 | + --focus-ring-color: slateblue; |
| 113 | + |
| 114 | + color: var(--text-color); |
| 115 | + background: var(--background-color); |
| 116 | + border: 1px solid var(--border-color); |
| 117 | + border-radius: 4px; |
| 118 | + appearance: none; |
| 119 | + vertical-align: middle; |
| 120 | + font-size: 1rem; |
| 121 | + text-align: center; |
| 122 | + margin: 0; |
| 123 | + outline: none; |
| 124 | + padding: 6px 10px; |
| 125 | + text-decoration: none; |
| 126 | + |
| 127 | + &[data-pressed] { |
| 128 | + box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1); |
| 129 | + background: var(--background-color-pressed); |
| 130 | + border-color: var(--border-color-pressed); |
| 131 | + } |
| 132 | + |
| 133 | + &[data-focus-visible] { |
| 134 | + border-color: var(--focus-ring-color); |
| 135 | + box-shadow: 0 0 0 1px var(--focus-ring-color); |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +@media (forced-colors: active) { |
| 140 | + .react-aria-TextField { |
| 141 | + --field-border: ButtonBorder; |
| 142 | + --field-border-disabled: GrayText; |
| 143 | + --field-background: Field; |
| 144 | + --text-color: FieldText; |
| 145 | + --text-color-disabled: GrayText; |
| 146 | + --focus-ring-color: Highlight; |
| 147 | + --invalid-color: LinkText; |
| 148 | + } |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +</details> |
| 153 | + |
| 154 | +## Features |
| 155 | + |
| 156 | +A group can be created with a `<div role="group">` or via the HTML [<fieldset>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset) element. The `Group` component supports additional UI states, and can be used standalone or as part of a larger pattern such as [NumberField](NumberField.html) or [DatePicker](Datepicker.html). |
| 157 | + |
| 158 | +* **Styleable** – Hover, keyboard focus, disabled, and invalid states are provided for easy styling. These states only apply when interacting with an appropriate input device, unlike CSS pseudo classes. |
| 159 | +* **Accessible** – Implemented using the ARIA "group" role by default, with optional support for the "region" landmark role. |
| 160 | + |
| 161 | +## Anatomy |
| 162 | + |
| 163 | +A group consists of a container element for a set of semantically related UI controls. It supports states such as hover, focus within, and disabled, which are useful to style visually adjoined children. |
| 164 | + |
| 165 | +```tsx |
| 166 | +import {Group} from 'react-aria-components'; |
| 167 | + |
| 168 | +<Group> |
| 169 | + {/* ... */} |
| 170 | +</Group> |
| 171 | +``` |
| 172 | + |
| 173 | +## Accessibility |
| 174 | + |
| 175 | +### Labeling |
| 176 | + |
| 177 | +Group accepts the `aria-label` and `aria-labelledby` attributes to provide an accessible label to the group as a whole. This is read by assistive technology when navigating into the group from outside. When the labels of each child element of the group do not provide sufficient context on their own, the group should receive an additional label. |
| 178 | + |
| 179 | +```tsx example |
| 180 | +<span id="label-id">Serial number</span> |
| 181 | +<Group aria-labelledby="label-id"> |
| 182 | + <Input size={3} aria-label="First 3 digits" placeholder="000" /> |
| 183 | + – |
| 184 | + <Input size={2} aria-label="Middle 2 digits" placeholder="00" /> |
| 185 | + – |
| 186 | + <Input size={4} aria-label="Last 4 digits" placeholder="0000" /> |
| 187 | +</Group> |
| 188 | +``` |
| 189 | + |
| 190 | +### Role |
| 191 | + |
| 192 | +By default, `Group` uses the [group](https://w3c.github.io/aria/#group) ARIA role. If the contents of the group is important enough to be included in the page table of contents, use `role="region"` instead, and ensure that an `aria-label` or `aria-labelledby` prop is assigned. |
| 193 | + |
| 194 | +```tsx |
| 195 | +<Group role="region" aria-label="Object details"> |
| 196 | + {/* ... */} |
| 197 | +</Group> |
| 198 | +``` |
| 199 | + |
| 200 | +If the `Group` component is used for styling purposes only, and does not include a set of related UI controls, then use `role="presentation"` instead. |
| 201 | + |
| 202 | +## Props |
| 203 | + |
| 204 | +<PropTable component={docs.exports.Group} links={docs.links} /> |
| 205 | + |
| 206 | +## Styling |
| 207 | + |
| 208 | +React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin `className` attribute which can be targeted using CSS selectors. These follow the `react-aria-ComponentName` naming convention. |
| 209 | + |
| 210 | +```css |
| 211 | +.react-aria-Group { |
| 212 | + /* ... */ |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +A custom `className` can also be specified on any component. This overrides the default `className` provided by React Aria with your own. |
| 217 | + |
| 218 | +```jsx |
| 219 | +<Group className="my-group"> |
| 220 | + {/* ... */} |
| 221 | +</Group> |
| 222 | +``` |
| 223 | + |
| 224 | +In addition, some components support multiple UI states (e.g. focused, placeholder, readonly, etc.). React Aria components expose states using data attributes, which you can target in CSS selectors. For example: |
| 225 | + |
| 226 | +```css |
| 227 | +.react-aria-Group[data-hovered] { |
| 228 | + /* ... */ |
| 229 | +} |
| 230 | + |
| 231 | +.react-aria-Group[data-focus-visible] { |
| 232 | + /* ... */ |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | +The states, selectors, and render props for `Group` are documented below. |
| 237 | + |
| 238 | +<StateTable properties={docs.exports.GroupRenderProps.properties} /> |
| 239 | + |
| 240 | +## Advanced customization |
| 241 | + |
| 242 | +### Contexts |
| 243 | + |
| 244 | +All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in [mergeProps](mergeProps.html)). |
| 245 | + |
| 246 | +<ContextTable components={['Group']} docs={docs} /> |
| 247 | + |
| 248 | +This example shows a `LabeledGroup` component that accepts a label and a group as children. It uses the [useId](useId.html) hook to generate a unique id for the label, and provides this to the group via the `aria-labelledby` prop. |
| 249 | + |
| 250 | +```tsx example |
| 251 | +import {LabelContext, GroupContext} from 'react-aria-components'; |
| 252 | +import {useId} from 'react-aria'; |
| 253 | + |
| 254 | +function LabeledGroup({children}) { |
| 255 | + let labelId = useId(); |
| 256 | + |
| 257 | + return ( |
| 258 | + <LabelContext.Provider value={{id: labelId, elementType: 'span'}}> |
| 259 | + <GroupContext.Provider value={{'aria-labelledby': labelId}}> |
| 260 | + {children} |
| 261 | + </GroupContext.Provider> |
| 262 | + </LabelContext.Provider> |
| 263 | + ); |
| 264 | +} |
| 265 | + |
| 266 | +<LabeledGroup> |
| 267 | + <Label>Expiration date</Label> |
| 268 | + <Group> |
| 269 | + <Input size={3} aria-label="Month" placeholder="mm" /> |
| 270 | + / |
| 271 | + <Input size={4} aria-label="Year" placeholder="yyyy" /> |
| 272 | + </Group> |
| 273 | +</LabeledGroup> |
| 274 | +``` |
0 commit comments