diff --git a/packages/@react-aria/table/docs/TableAnatomy.svg b/packages/@react-aria/table/docs/TableAnatomy.svg
new file mode 100644
index 00000000000..89841bcdfd5
--- /dev/null
+++ b/packages/@react-aria/table/docs/TableAnatomy.svg
@@ -0,0 +1,165 @@
+
diff --git a/packages/@react-aria/table/docs/useTable.mdx b/packages/@react-aria/table/docs/useTable.mdx
new file mode 100644
index 00000000000..e36b75a2281
--- /dev/null
+++ b/packages/@react-aria/table/docs/useTable.mdx
@@ -0,0 +1,745 @@
+
+
+import {Layout} from '@react-spectrum/docs';
+export default Layout;
+
+import docs from 'docs:@react-aria/table';
+import collectionsDocs from 'docs:@react-types/shared/src/collections.d.ts';
+import selectionDocs from 'docs:@react-stately/selection';
+import statelyDocs from 'docs:@react-stately/table';
+import focusDocs from 'docs:@react-aria/focus';
+import checkboxDocs from 'docs:@react-aria/checkbox';
+import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink} from '@react-spectrum/docs';
+import packageData from '@react-aria/table/package.json';
+import Anatomy from './TableAnatomy.svg';
+
+```jsx import
+import {useTable, useTableCell, useTableColumnHeader, useTableRow, useTableHeaderRow, useTableRowGroup, useTableSelectAllCheckbox, useTableSelectionCheckbox} from '@react-aria/table';
+```
+
+---
+category: Collections
+keywords: [table, aria, grid]
+---
+
+# useTable
+
+
{docs.exports.useTable.description}
+
+
+
+## API
+
+
+
+
+
+
+
+
+
+
+## Features
+
+A table can be built using the [<table>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table), [<tr>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr),
+[<td>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td), and other table specific HTML elements, but is very limited in functionality especially when it comes to user interactions.
+HTML tables are meant for static content, rather than tables with rich interactions like focusable elements within cells, keyboard navigation, row selection, sorting, etc.
+`useTable` helps achieve accessible and interactive table components that can be styled as needed.
+
+* Exposed to assistive technology as a `grid` using ARIA
+* Keyboard navigation between columns, rows, cells, and in-cell focusable elements via the arrow keys
+* Single, multiple, or no row selection via mouse, touch, or keyboard interactions
+* Support for disabled rows, which cannot be selected
+* Optional support for checkboxes in each row for selection, as well as in the header to select all rows
+* Column sorting support
+* Async loading, infinite scrolling, filtering, and sorting support
+* Support for column groups via nested columns
+* Typeahead to allow focusing rows by typing text
+* Automatic scrolling support during keyboard navigation
+* Labeling support for accessibility
+* Support for marking columns as [row headers](https://www.w3.org/TR/wai-aria-1.1/#rowheader), which will be read when navigating the rows with a screen reader
+* Ensures that selections are announced using an ARIA live region
+* Support for using HTML table elements, or custom element types (e.g. `
`) for layout flexibility
+* Virtualized scrolling support for performance with large tables
+
+## Anatomy
+
+
+
+A table consists of a container element, with columns and rows of cells containing data inside. The cells within a table may contain focusable elements or plain text content.
+If the table supports row selection, each row can optionally include a selection checkbox in the first column. Additionally, a "select all" checkbox is displayed as the first column header if the table supports multiple row selection.
+
+The , , , and hooks handle keyboard, mouse, and other interactions to support
+row selection, in table navigation, and overall focus behavior. Those hooks, along with and , also handle exposing the table and its contents
+to assistive technology using ARIA. and handle row selection and associating each checkbox with its respective rows
+for assistive technology. Each of these hooks returns props to be spread onto the appropriate HTML element.
+
+State is managed by the
+hook from `@react-stately/table`. The state object should be passed as an option to each of the above hooks where applicable.
+
+Note that an `aria-label` or `aria-labelledby` must be passed to the table to identify the element to assistive technology.
+
+## State management
+
+`useTable` requires knowledge of the rows, cells, and columns in the table in order to handle keyboard
+navigation and other interactions. It does this using
+the
+interface, which is a generic interface to access sequential unique keyed data. You can
+implement this interface yourself, e.g. by using a prop to pass a list of item objects,
+but from
+`@react-stately/table` implements a JSX based interface for building collections instead.
+See [Collection Components](/react-stately/collections.html) for more information,
+and [Collection Interface](/react-stately/Collection.html) for internal details.
+
+Data is defined using the , , , , and components, which support both static and dynamic data.
+See the examples in the [usage](#usage) section below for details on how to use these components.
+
+In addition,
+manages the state necessary for multiple selection and exposes
+a ,
+which makes use of the collection to provide an interface to update the selection state.
+For more information, see [Selection](/react-stately/selection.html).
+
+## Example
+
+Tables are complex [collection components](../react-stately/collections.html) that are built up from many child elements
+including columns, rows, and cells. In this example, we'll use the standard HTML table elements along with hooks from React
+Aria for each child. You may also use other elements like `
` to render these components as appropriate.
+Since there are many pieces, we'll walk through each of them one by one.
+
+The hook will be used to render the outer most table element. It uses
+the hook to construct the table's collection of rows and columns,
+and manage state such as the focused row/cell, selection, and sort column/direction. We'll use the collection to iterate through
+the rows and cells of the table and render the relevant components, which we'll define below.
+
+```tsx example export=true render=false
+import {Cell, Column, Row, TableBody, TableHeader, useTableState} from '@react-stately/table';
+import {mergeProps} from '@react-aria/utils';
+import {useRef} from 'react';
+import {useFocusRing} from '@react-aria/focus';
+
+function Table(props) {
+ let state = useTableState({...props, showSelectionCheckboxes: props.selectionMode === 'multiple'});
+ let ref = useRef();
+ let {collection} = state;
+ let {gridProps} = useTable(props, state, ref);
+
+ return (
+
+ );
+}
+```
+
+### Table header
+
+A hook will be used to group the rows in the table header and table body. In this example,
+we're using HTML table elements, so this will be either a `` or `` element, as passed from the
+above `Table` component via the `type` prop.
+
+```tsx example export=true render=false
+function TableRowGroup({type: Element, style, children}) {
+ let {rowGroupProps} = useTableRowGroup();
+ return (
+
+ {children}
+
+ );
+}
+```
+
+The hook will be used to render a header row. Header rows are similar to other rows,
+but they don't support user interaction like selection. In this example, there's only one header
+row, but there could be multiple in the case of nested columns. See the [example below](#nested-columns) for details.
+
+```tsx example export=true render=false
+function TableHeaderRow({item, state, children}) {
+ let ref = useRef();
+ let {rowProps} = useTableHeaderRow({node: item}, state, ref);
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+The hook will be used to render each column header. Column headers act as a label
+for all of the cells in that column, and can optionally support user interaction to sort by the column
+and change the sort order.
+
+The `allowsSorting` property of the column object can be used to determine
+if the column supports sorting at all.
+
+The `sortDescriptor` object stored in the `state` object indicates which column the table is currently sorted by,
+as well as the sort direction (ascending or descending). This is used to render an arrow icon to visually
+indicate the sort direction. When not sorted by this column, we use `visibility: hidden` to ensure that
+we reserve space for this icon at all times. That way the table's layout doesn't shift when we change the
+column we're sorting by. See the [example below](#sorting) of all of this in action.
+
+Finally, we use the hook to ensure that a focus ring is rendered when
+the cell is navigated to with the keyboard.
+
+```tsx example export=true render=false
+function TableColumnHeader({column, state}) {
+ let ref = useRef();
+ let {columnHeaderProps} = useTableColumnHeader({node: column}, state, ref);
+ let {isFocusVisible, focusProps} = useFocusRing();
+ let arrowIcon = state.sortDescriptor?.direction === 'ascending' ? '▲' : '▼';
+
+ return (
+
+ );
+}
+```
+
+### Table body
+
+Now that we've covered the table header, let's move on to the body. We'll use
+the hook to render each row in the table.
+Table rows can be focused and navigated to using the keyboard via the arrow keys. In addition, table rows
+can optionally support selection via mouse, touch, or keyboard. Clicking, tapping, or pressing the Space
+key anywhere in the row selects it.
+
+We'll use the object exposed
+by the `state` to determine if a row is selected, and render a pink background if so. We'll also use the
+hook to render a focus ring when the user navigates to the row with the keyboard.
+
+```tsx example export=true render=false
+function TableRow({item, children, state}) {
+ let ref = useRef();
+ let isSelected = state.selectionManager.isSelected(item.key);
+ let {rowProps} = useTableRow({node: item}, state, ref);
+ let {isFocusVisible, focusProps} = useFocusRing();
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Finally, we'll use the hook to render each cell.
+Users can use the left and right arrow keys to navigate to each cell in a row, as well as any focusable elements
+within a cell. This is indicated by the focus ring, as created with the
+hook. The cell's contents are available in the `rendered` property of the cell
+object.
+
+```tsx example export=true render=false
+function TableCell({cell, state}) {
+ let ref = useRef();
+ let {gridCellProps} = useTableCell({node: cell}, state, ref);
+ let {isFocusVisible, focusProps} = useFocusRing();
+
+ return (
+
+ {cell.rendered}
+
+ );
+}
+```
+
+With all of the above components in place, we can render an example of our Table in action.
+This example shows a static collection, where all of the data is hard coded. [See below](#dynamic-collections)
+for examples of using this Table component with dynamic collections (e.g. from a server).
+
+Try tabbing into the table and navigating using the arrow keys.
+
+```tsx example
+
+
+ Name
+ Type
+ Date Modified
+
+
+
+ Games
+ File folder
+ 6/7/2020
+
+
+ Program Files
+ File folder
+ 4/7/2021
+
+
+ bootmgr
+ System file
+ 11/20/2010
+
+
+ log.txt
+ Text Document
+ 1/18/2016
+
+
+
+```
+
+### Adding selection
+
+Next, let's add support for selection. For multiple selection, we'll want to add a column of checkboxes to the left
+of the table to allow the user to select rows. This is done using the
+hook. It is passed the `parentKey` of the cell, which refers to the row the cell is contained within. When the user
+checks or unchecks the checkbox, the row will be added or removed from the Table's selection.
+
+In this example, we pass the result of the `checkboxProps` into the
+hook and render an `` element directly, but it's likely you'll have a `Checkbox` component in your component library that uses these hooks already.
+See the [useCheckbox docs](useCheckbox.html) for more information.
+
+```tsx example export=true render=false
+import {useToggleState} from '@react-stately/toggle';
+import {useCheckbox} from '@react-aria/checkbox';
+
+function TableCheckboxCell({cell, state}) {
+ let ref = useRef();
+ let {gridCellProps} = useTableCell({node: cell}, state, ref);
+ let {checkboxProps} = useTableSelectionCheckbox({key: cell.parentKey}, state);
+
+ let inputRef = useRef(null);
+ let {inputProps} = useCheckbox(checkboxProps, useToggleState(checkboxProps), inputRef);
+
+ return (
+
+
+
+ );
+}
+```
+
+We also want the user to be able to select all rows in the table at once. This is possible using the ⌘ Cmd + A
+keyboard shortcut, but we'll also add a checkbox into the table header to do this and represent the selection state visually.
+This is done using the hook. When all rows are selected,
+the checkbox will be shown as checked, and when only some rows are selected, the checkbox will be rendered in an indeterminate state.
+The user can check or uncheck the checkbox to select all or clear the selection, respectively.
+
+```tsx example export=true render=false
+function TableSelectAllCell({column, state}) {
+ let ref = useRef();
+ let isSingleSelectionMode = state.selectionManager.selectionMode === 'single';
+ let {columnHeaderProps} = useTableColumnHeader({node: column}, state, ref);
+
+ let {checkboxProps} = useTableSelectAllCheckbox(state);
+ let inputRef = useRef(null);
+ let {inputProps} = useCheckbox(checkboxProps, useToggleState(checkboxProps), inputRef);
+
+ return (
+
+
+
+ );
+}
+```
+
+The following example shows how to enable multiple selection support using the Table component we built above.
+It's as simple as setting the `selectionMode` prop to `"multiple"`. Because we set the `showSelectionCheckboxes`
+option of `useTableState` to true when multiple selection is enabled, an extra column for these checkboxes is
+automatically added for us.
+
+```tsx example
+
+```
+
+And that's it! We now have a fully interactive table component that can support keyboard navigation, single or multiple selection,
+as well as column sorting. In addition, it is fully accessible for screen readers and other assistive technology. See below for more
+examples of how to use the Table component that we've built.
+
+## Usage
+
+### Dynamic collections
+
+So far, our examples have shown static collections, where the data is hard coded.
+Dynamic collections, as shown below, can be used when the table data comes from an external data source such as an API, or updates over time.
+In the example below, both the columns and the rows are provided to the table via a render function. You can also make the columns static and
+only the rows dynamic.
+
+```tsx example export=true
+function ExampleTable(props) {
+ let columns = [
+ {name: 'Name', key: 'name'},
+ {name: 'Type', key: 'type'},
+ {name: 'Date Modified', key: 'date'}
+ ];
+
+ let rows = [
+ {id: 1, name: 'Games', date: '6/7/2020', type: 'File folder'},
+ {id: 2, name: 'Program Files', date: '4/7/2021', type: 'File folder'},
+ {id: 3, name: 'bootmgr', date: '11/20/2010', type: 'System file'},
+ {id: 4, name: 'log.txt', date: '1/18/2016', type: 'Text Document'}
+ ];
+
+ return (
+
+ );
+}
+```
+
+### Single selection
+
+By default, `useTableState` doesn't allow row selection but this can be enabled using the `selectionMode` prop. Use `defaultSelectedKeys` to provide a default set of selected rows.
+Note that the value of the selected keys must match the `key` prop of the row.
+
+The example below enables single selection mode, and uses `defaultSelectedKeys` to select the row with key equal to "2".
+A user can click on a different row to change the selection, or click on the same row again to deselect it entirely.
+
+```tsx example
+// Using the example above
+
+```
+
+### Multiple selection
+
+Multiple selection can be enabled by setting `selectionMode` to `multiple`.
+
+```tsx example
+// Using the example above
+
+```
+
+### Disallow empty selection
+
+Table also supports a `disallowEmptySelection` prop which forces the user to have at least one row in the Table selected at all times.
+In this mode, if a single row is selected and the user presses it, it will not be deselected.
+
+```tsx example
+// Using the example above
+
+```
+
+### Controlled selection
+
+To programmatically control row selection, use the `selectedKeys` prop paired with the `onSelectionChange` callback. The `key` prop from the selected rows will
+be passed into the callback when the row is pressed, allowing you to update state accordingly.
+
+```tsx example export=true
+function PokemonTable(props) {
+ let columns = [
+ {name: 'Name', uid: 'name'},
+ {name: 'Type', uid: 'type'},
+ {name: 'Level', uid: 'level'}
+ ];
+
+ let rows = [
+ {id: 1, name: 'Charizard', type: 'Fire, Flying', level: '67'},
+ {id: 2, name: 'Blastoise', type: 'Water', level: '56'},
+ {id: 3, name: 'Venusaur', type: 'Grass, Poison', level: '83'},
+ {id: 4, name: 'Pikachu', type: 'Electric', level: '100'}
+ ];
+
+ let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));
+
+ return (
+
+ );
+}
+```
+
+### Disabled rows
+
+You can disable specific rows by providing an array of keys to `useTableState` via the `disabledKeys` prop. This will prevent rows from being selectable as shown in the example below.
+Note that you are responsible for the styling of disabled rows, however, the selection checkbox will be automatically disabled.
+
+```tsx example
+// Using the same table as above
+
+```
+
+### Sorting
+
+Table supports sorting its data when a column header is pressed. To designate that a Column should support sorting, provide it with
+the `allowsSorting` prop. The Table accepts a `sortDescriptor` prop that defines the current column key to sort by and the sort direction (ascending/descending).
+When the user presses a sortable column header, the column's key and sort direction is passed into the `onSortChange` callback, allowing you to update
+the `sortDescriptor` appropriately.
+
+This example performs client side sorting by passing a `sort` function to the [useAsyncList](../react-stately/useAsyncList.html) hook.
+See the docs for more information on how to perform server side sorting.
+
+```tsx example
+import {useAsyncList} from '@react-stately/data';
+
+function AsyncSortTable() {
+ let list = useAsyncList({
+ async load({signal}) {
+ let res = await fetch(`https://swapi.dev/api/people/?search`, {signal});
+ let json = await res.json();
+ return {
+ items: json.results
+ };
+ },
+ async sort({items, sortDescriptor}) {
+ return {
+ items: items.sort((a, b) => {
+ let first = a[sortDescriptor.column];
+ let second = b[sortDescriptor.column];
+ let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
+ if (sortDescriptor.direction === 'descending') {
+ cmp *= -1;
+ }
+ return cmp;
+ })
+ };
+ }
+ });
+
+ return (
+
+
+ Name
+ Height
+ Mass
+ Birth Year
+
+
+ {item => (
+
+ {columnKey => {item[columnKey]}}
+
+ )}
+
+
+ );
+}
+```
+
+### Nested columns
+
+Columns can be nested to create column groups. This will result in more than one header row to be created, with the `colspan`
+attribute of each column header cell set to the appropriate value so that the columns line up. Data for the leaf columns
+appears in each row of the table body.
+
+This example also shows the use of the `isRowHeader` prop for `Column`, which controls which columns are included in the
+accessibility name for each row. By default, only the first column is included, but in some cases more than one column may
+be used to represent the row. In this example, the first and last name columns are combined to form the ARIA label for the row.
+Only leaf columns may be marked as row headers.
+
+```tsx example
+
+
+
+ First Name
+ Last Name
+
+
+ Age
+ Birthday
+
+
+
+
+ Sam
+ Smith
+ 36
+ May 3
+
+
+ Julia
+ Jones
+ 24
+ February 10
+
+
+ Peter
+ Parker
+ 28
+ September 7
+
+
+ Bruce
+ Wayne
+ 32
+ December 18
+
+
+
+```
+
+### Dynamic nested columns
+
+Nested columns can also be defined dynamically using the function syntax and the `childColumns` prop.
+The following example is the same as the example above, but defined dynamically.
+
+```tsx example
+let columns = [
+ {name: 'Name', key: 'name', children: [
+ {name: 'First Name', key: 'first', isRowHeader: true},
+ {name: 'Last Name', key: 'last', isRowHeader: true}
+ ]},
+ {name: 'Information', key: 'info', children: [
+ {name: 'Age', key: 'age'},
+ {name: 'Birthday', key: 'birthday'}
+ ]}
+];
+
+let rows = [
+ {id: 1, first: 'Sam', last: 'Smith', age: 36, birthday: 'May 3'},
+ {id: 2, first: 'Julia', last: 'Jones', age: 24, birthday: 'February 10'},
+ {id: 3, first: 'Peter', last: 'Parker', age: 28, birthday: 'September 7'},
+ {id: 4, first: 'Bruce', last: 'Wayne', age: 32, birthday: 'December 18'}
+];
+
+
+```
+
+## Internationalization
+
+`useTable` handles some aspects of internationalization automatically.
+For example, type to select is implemented with an
+[Intl.Collator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator)
+for internationalized string matching, and keyboard navigation is mirrored in right-to-left languages.
+You are responsible for localizing all text content within the table.
+
+### RTL
+
+In right-to-left languages, the table layout should be mirrored. The columns should be ordered from right to left and the
+individual column text alignment should be inverted. Ensure that your CSS accounts for this.
diff --git a/packages/@react-spectrum/table/docs/TableView.mdx b/packages/@react-spectrum/table/docs/TableView.mdx
new file mode 100644
index 00000000000..b10abf9ce98
--- /dev/null
+++ b/packages/@react-spectrum/table/docs/TableView.mdx
@@ -0,0 +1,868 @@
+
+
+import {Layout} from '@react-spectrum/docs';
+export default Layout;
+
+import docs from 'docs:@react-spectrum/table';
+import tableTypes from 'docs:@react-types/table/src/index.d.ts';
+import {HeaderInfo, PropTable} from '@react-spectrum/docs';
+import packageData from '@react-spectrum/table/package.json';
+
+```jsx import
+import {ActionButton} from '@react-spectrum/button';
+import Add from '@spectrum-icons/workflow/Add';
+import {Cell, Column, Row, TableView, TableBody, TableHeader} from '@react-spectrum/table';
+import {Flex} from '@react-spectrum/layout';
+```
+
+---
+category: Collections
+keywords: [table, grid]
+---
+
+# TableView
+
+
{docs.exports.TableView.description}
+
+
+
+## Example
+```tsx example
+
+
+ Name
+ Type
+ Date Modified
+
+
+
+ Games
+ File folder
+ 6/7/2020
+
+
+ Program Files
+ File folder
+ 4/7/2021
+
+
+ bootmgr
+ System file
+ 11/20/2010
+
+
+ log.txt
+ Text Document
+ 1/18/2016
+
+
+
+```
+
+## Content
+TableView is a complex [collection component](../react-stately/collections.html) that is built up from many child elements including columns, rows, and cells. Columns are defined within a TableHeader element and rows are defined within a TableBody element. Rows contain Cell elements that correspond to each column. Cells can contain any element, allowing you to have focusable children within the TableView.
+
+Basic usage of TableView, seen in the example above, shows the use of a static collection where the contents of the TableView is hard coded. Dynamic collections, as shown below, can be used when the options come from an external data source such as an API, or update over time. Providing the data in this way allows TableView to automatically cache the rendering of each item, which dramatically improves performance.
+
+Columns and rows can be statically defined as children, or generated dynamically using a function based on the data passed to the `columns` or `items` prop respectively. Cells can also be statically defined as children, or generated dynamically based on the columns defined in the TableHeader.
+
+Each column and row has a unique key defined by the data. In the example below, the `uid` property of the column object is used as the `key` for the Column element within the TableHeader. The `key` of each row element is implicitly defined by the id property of the row object. See [collections](../react-stately/collections.html#unique-keys) to learn more keys in dynamic collections.
+
+```tsx example
+let columns = [
+ {name: 'Name', uid: 'name'},
+ {name: 'Type', uid: 'type'},
+ {name: 'Date Modified', uid: 'date'}
+];
+
+let rows = [
+ {id: 1, name: 'Games', date: '6/7/2020', type: 'File folder'},
+ {id: 2, name: 'Program Files', date: '4/7/2021', type: 'File folder'},
+ {id: 3, name: 'bootmgr', date: '11/20/2010', type: 'System file'},
+ {id: 4, name: 'log.txt', date: '1/18/2016', type: 'Text Document'}
+];
+
+
+
+ {column => (
+
+ {column.name}
+
+ )}
+
+
+ {item => (
+
+ {columnKey => {item[columnKey]}}
+
+ )}
+
+
+```
+### Layout
+TableViews are often contained within a page layout that defines the size of the table. For example, a page might have a header or toolbar with a TableView below that fills the remaining vertical space. TableView is designed to scroll internally while the column headers remain fixed. Because of this, TableViews should not be placed within a scrollable container.
+
+The example below shows how to use a [Flex](Flex.html) component to achieve the layout described above. Note the TableView uses the `flex` prop to fill the remainder of the available space.
+
+
+```tsx example
+///- begin collapse -///
+let columns = [
+ {name: 'First name', id: 'first_name'},
+ {name: 'Last name', id: 'last_name'},
+ {name: 'City', id: 'city'}
+];
+
+let rows = [
+{"id":1,"first_name":"Andras","last_name":"Rodmell","city":"Tilburg"},
+{"id":2,"first_name":"Hansiain","last_name":"Muino","city":"Hollola"},
+{"id":3,"first_name":"Northrop","last_name":"Adnet","city":"Lai Cách"},
+{"id":4,"first_name":"Giana","last_name":"Phython","city":"Laspezia"},
+{"id":5,"first_name":"Maud","last_name":"Jaram","city":"Tipaz"},
+{"id":6,"first_name":"Gasparo","last_name":"Wiggin","city":"Feuknoni"},
+{"id":7,"first_name":"Phillie","last_name":"Lezemere","city":"Krajan Sidodadi"},
+{"id":8,"first_name":"Kailey","last_name":"Du Plantier","city":"Shangping"},
+{"id":9,"first_name":"Brady","last_name":"Oxtarby","city":"Bang Mun Nak"},
+{"id":10,"first_name":"Ekaterina","last_name":"Crennan","city":"Santo Antônio do Amparo"},
+{"id":11,"first_name":"Jaine","last_name":"Trembey","city":"Manūjān"},
+{"id":12,"first_name":"Emmey","last_name":"Dunguy","city":"Garhi Yāsīn"},
+{"id":13,"first_name":"Camille","last_name":"Millwall","city":"Orion"},
+{"id":14,"first_name":"Staci","last_name":"Glusby","city":"Alofi"},
+{"id":15,"first_name":"Ned","last_name":"Crumbleholme","city":"Ban Bueng"},
+{"id":16,"first_name":"Tana","last_name":"Beardsworth","city":"Puerto Aisén"},
+{"id":17,"first_name":"Dewain","last_name":"Fladgate","city":"London"},
+{"id":18,"first_name":"Thurstan","last_name":"Trembath","city":"Orléans"},
+{"id":19,"first_name":"Vaclav","last_name":"Fitzpayn","city":"Huangchen"},
+{"id":20,"first_name":"Keven","last_name":"Monkeman","city":"Medenychi"},
+{"id":21,"first_name":"Talia","last_name":"Ryman","city":"Piteå"},
+{"id":22,"first_name":"Percy","last_name":"Le Teve","city":"Terny"},
+{"id":23,"first_name":"Jackson","last_name":"Anten","city":"Beiling"},
+{"id":24,"first_name":"Jakob","last_name":"Goullee","city":"Pelym"},
+{"id":25,"first_name":"Dru","last_name":"Klainer","city":"Zavrč"},
+{"id":26,"first_name":"Lucie","last_name":"Donahue","city":"Kiryū"},
+{"id":27,"first_name":"Marc","last_name":"McPeck","city":"Nong Muang Khai"},
+{"id":28,"first_name":"Vivianna","last_name":"Allport","city":"Kajatian"},
+{"id":29,"first_name":"Drud","last_name":"Hurn","city":"Bambuí"},
+{"id":30,"first_name":"Trever","last_name":"Ambrodi","city":"Xiangtan"},
+{"id":31,"first_name":"Gwennie","last_name":"Kingswold","city":"San Benito"},
+{"id":32,"first_name":"Karlan","last_name":"Tilby","city":"Patrída"},
+{"id":33,"first_name":"Heddie","last_name":"Sneath","city":"Esperanza"},
+{"id":34,"first_name":"Harlen","last_name":"Sandells","city":"Harrismith"},
+{"id":35,"first_name":"Gavan","last_name":"Halward","city":"Al Ḩayfah"},
+{"id":36,"first_name":"Andre","last_name":"Everest","city":"Bahui"},
+{"id":37,"first_name":"Merilyn","last_name":"Rowbrey","city":"Imishli"},
+{"id":38,"first_name":"Abe","last_name":"Pecht","city":"Pangkalan Kasai"},
+{"id":39,"first_name":"Britt","last_name":"Collingridge","city":"Érd"},
+{"id":40,"first_name":"Leticia","last_name":"Thorndycraft","city":"Paita"},
+{"id":41,"first_name":"Eward","last_name":"Tigwell","city":"Aral"},
+{"id":42,"first_name":"Torrie","last_name":"Curzon","city":"Stockholm"},
+{"id":43,"first_name":"Jenifer","last_name":"Swalwel","city":"Jinniu"},
+{"id":44,"first_name":"Marianna","last_name":"Radley","city":"Hedi"},
+{"id":45,"first_name":"Antoine","last_name":"Tyers","city":"Hewa"},
+{"id":46,"first_name":"Darline","last_name":"Gallehawk","city":"København"},
+{"id":47,"first_name":"Rikki","last_name":"Rosenzveig","city":"Affery"},
+{"id":48,"first_name":"Debera","last_name":"Vedenichev","city":"Żywiec"},
+{"id":49,"first_name":"Morena","last_name":"Hewins","city":"Las Lajas"},
+{"id":50,"first_name":"Cordy","last_name":"Reimer","city":"Derbent"},
+{"id":51,"first_name":"Quint","last_name":"Thoresbie","city":"Guyang"},
+{"id":52,"first_name":"Christean","last_name":"Deere","city":"Waegwan"},
+{"id":53,"first_name":"Moyra","last_name":"Battelle","city":"Villa Presidente Frei, Ñuñoa, Santiago, Chile"},
+{"id":54,"first_name":"Fayth","last_name":"Gallafant","city":"Kedungharjo"},
+{"id":55,"first_name":"Thedrick","last_name":"Duddy","city":"Thị Trấn Mường Lát"},
+{"id":56,"first_name":"George","last_name":"Rickerd","city":"Zarqa"},
+{"id":57,"first_name":"Nikos","last_name":"Rideout","city":"Yuanqiao"},
+{"id":58,"first_name":"Alejandra","last_name":"Le Port","city":"Il’ichëvo"},
+{"id":59,"first_name":"Eleonora","last_name":"Gibberd","city":"Sua"},
+{"id":60,"first_name":"Archibaldo","last_name":"Place","city":"Sidayu"},
+{"id":61,"first_name":"Helen","last_name":"Brenton","city":"Kuressaare"},
+{"id":62,"first_name":"Leyla","last_name":"Armstead","city":"Haifa"},
+{"id":63,"first_name":"Bridget","last_name":"Strotone","city":"Karasuk"},
+{"id":64,"first_name":"Jarid","last_name":"Packer","city":"Студеничани"},
+{"id":65,"first_name":"Christos","last_name":"Natt","city":"Nova Russas"},
+{"id":66,"first_name":"Alwyn","last_name":"Mingaud","city":"Conde"},
+{"id":67,"first_name":"Archy","last_name":"Thorneywork","city":"Gulu"},
+{"id":68,"first_name":"Iolanthe","last_name":"Spurgeon","city":"Ayrihuanca"},
+{"id":69,"first_name":"Rossy","last_name":"Axford","city":"Ledeč nad Sázavou"},
+{"id":70,"first_name":"Consuela","last_name":"Lillegard","city":"Finote Selam"},
+{"id":71,"first_name":"Salomon","last_name":"Buckney","city":"Kampokpok"},
+{"id":72,"first_name":"Celene","last_name":"Espley","city":"Sinubong"},
+{"id":73,"first_name":"Kristos","last_name":"Denyukhin","city":"Las Palmas"},
+{"id":74,"first_name":"Bertha","last_name":"Mallabon","city":"Vera"},
+{"id":75,"first_name":"Jorry","last_name":"Yuryev","city":"Carletonville"},
+{"id":76,"first_name":"Holly-anne","last_name":"Wagstaffe","city":"Sukadana"},
+{"id":77,"first_name":"Lara","last_name":"Shears","city":"Gambēla"},
+{"id":78,"first_name":"Romonda","last_name":"Glanville","city":"Donglu"},
+{"id":79,"first_name":"Felice","last_name":"Pryde","city":"Sapadun"},
+{"id":80,"first_name":"Nick","last_name":"Kidney","city":"Chernigovka"},
+{"id":81,"first_name":"Hermina","last_name":"Dooley","city":"New Agutaya"},
+{"id":82,"first_name":"Ketty","last_name":"FitzGeorge","city":"Abaza"},
+{"id":83,"first_name":"Patrizio","last_name":"Bovingdon","city":"‘Ayn al ‘Arab"},
+{"id":84,"first_name":"Caitrin","last_name":"Braine","city":"Il’inskiy"},
+{"id":85,"first_name":"Ian","last_name":"De Few","city":"Jatinagara"},
+{"id":86,"first_name":"Eben","last_name":"Adan","city":"Bolong"},
+{"id":87,"first_name":"Peder","last_name":"Innott","city":"Gampaha"},
+{"id":88,"first_name":"Selie","last_name":"Cruise","city":"Mariscala"},
+{"id":89,"first_name":"Melania","last_name":"Meredyth","city":"La’ershan"},
+{"id":90,"first_name":"Antonina","last_name":"Proby","city":"Shantoudian"},
+{"id":91,"first_name":"Sabra","last_name":"Dreng","city":"Dzhankoy"},
+{"id":92,"first_name":"Sibeal","last_name":"Hall-Gough","city":"Mengxi"},
+{"id":93,"first_name":"Fidel","last_name":"Maisey","city":"Gus’-Khrustal’nyy"},
+{"id":94,"first_name":"Alejandro","last_name":"Devey","city":"Charata"},
+{"id":95,"first_name":"Norina","last_name":"Stoyle","city":"Malaya Dubna"},
+{"id":96,"first_name":"Lari","last_name":"Kiezler","city":"Guaíba"},
+{"id":97,"first_name":"Percival","last_name":"Geffinger","city":"Ngeni"},
+{"id":98,"first_name":"Jo","last_name":"Spoure","city":"Karata"},
+{"id":99,"first_name":"Karlie","last_name":"Gooddy","city":"Pelem"},
+{"id":100,"first_name":"Edmon","last_name":"Alsopp","city":"Sandu"}];
+///- end collapse -///
+
+
+ Add
+
+
+ {column => (
+
+ {column.name}
+
+ )}
+
+
+ {item => (
+
+ {columnKey => {item[columnKey]}}
+
+ )}
+
+
+
+```
+
+### Internationalization
+To internationalize a TableView, all text content within the TableView should be localized. This includes the `aria-label` provided to the TableView if any.
+For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of TableView is automatically flipped.
+
+## Labeling
+### Accessibility
+An `aria-label` must be provided to the TableView for accessibility. If the TableView is labeled by a separate element, an `aria-labelledby` prop must be provided using the id of the labeling element instead.
+
+By default, the first column of the TableView is used as the [row header](https://www.w3.org/TR/wai-aria-1.1/#rowheader) and is announced by assistive technology when navigating through the rows. You can override this behavior by providing the `isRowHeader` prop
+to one or more Columns, allowing you to customize which columns should label the rows of the TableView.
+
+The example below applies `isRowHeader` to the "First Name" and "Last Name" columns so that each row is announced with the person's full name (e.g. "John Doe").
+
+```tsx example
+
+
+ First Name
+ Last Name
+ Age
+
+
+
+ John
+ Doe
+ 45
+
+
+ Jane
+ Doe
+ 37
+
+
+ Joe
+ Schmoe
+ 67
+
+
+
+```
+
+## Asynchronous loading
+TableView supports loading data asynchronously, and will display a progress circle reflecting the current load state,
+set by the `loadingState` prop. It also supports infinite scrolling to load more data on demand as the user scrolls, via the `onLoadMore` prop.
+
+This example uses the [useAsyncList](../react-stately/useAsyncList.html) hook to handle loading the data.
+See the docs for more information.
+
+```tsx example
+import {useAsyncList} from '@react-stately/data';
+
+function AsyncTable() {
+ let columns = [
+ {name: 'Name', key: 'name'},
+ {name: 'Height', key: 'height'},
+ {name: 'Mass', key: 'mass'},
+ {name: 'Birth Year', key: 'birth_year'}
+ ];
+
+ let list = useAsyncList({
+ async load({signal, cursor}) {
+ if (cursor) {
+ cursor = cursor.replace(/^http:\/\//i, 'https://');
+ }
+
+ let res = await fetch(cursor || `https://swapi.dev/api/people/?search=`, {signal});
+ let json = await res.json();
+
+ return {
+ items: json.results,
+ cursor: json.next
+ };
+ }
+ });
+
+ return (
+
+
+ {(column) => (
+
+ {column.name}
+
+ )}
+
+
+ {(item) => (
+ {(key) => {item[key]}}
+ )}
+
+
+ );
+}
+```
+
+
+## Selection
+
+By default, TableView doesn't allow row selection but this can be enabled using the `selectionMode` prop.
+Use `defaultSelectedKeys` to provide a default set of selected rows. Note that the value of the selected keys must match the `key` prop of the Row.
+
+The example below enables multiple selection mode, and uses `defaultSelectedKeys` to select the rows with keys "2" and "4".
+
+```tsx example
+
+
+ Name
+ Type
+ Level
+
+
+
+ Charizard
+ Fire, Flying
+ 67
+
+
+ Blastoise
+ Water
+ 56
+
+
+ Venusaur
+ Grass, Poison
+ 83
+
+
+ Pikachu
+ Electric
+ 100
+
+
+
+```
+
+### Controlled selection
+
+To programmatically control row selection, use the `selectedKeys` prop paired with the `onSelectionChange` callback. The `key` prop from the selected rows will
+be passed into the callback when the row is pressed, allowing you to update state accordingly.
+
+Here is how you would control selection for the above example.
+
+```tsx example export=true
+function PokemonTable(props) {
+ let columns = [
+ {name: 'Name', uid: 'name'},
+ {name: 'Type', uid: 'type'},
+ {name: 'Level', uid: 'level'}
+ ];
+
+ let rows = [
+ {id: 1, name: 'Charizard', type: 'Fire, Flying', level: '67'},
+ {id: 2, name: 'Blastoise', type: 'Water', level: '56'},
+ {id: 3, name: 'Venusaur', type: 'Grass, Poison', level: '83'},
+ {id: 4, name: 'Pikachu', type: 'Electric', level: '100'}
+ ];
+
+ let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));
+
+ return (
+
+
+ {column => (
+
+ {column.name}
+
+ )}
+
+
+ {item => (
+
+ {columnKey => {item[columnKey]}}
+
+ )}
+
+
+ );
+}
+```
+
+### Single selection
+
+To limit users to selecting only a single item at a time, `selectionMode` can be set to `single`.
+
+```tsx example
+// Using the same table as above
+
+```
+
+### Disallow empty selection
+
+TableView also supports a `disallowEmptySelection` prop which forces the user to have at least one row in the TableView selected at all times.
+In this mode, if a single row is selected and the user presses it, it will not be deselected.
+
+```tsx example
+// Using the same table as above
+
+```
+
+### Disabled rows
+
+You can disable specific rows by providing an array of keys to TableView via the `disabledKeys` prop. This will prevent rows from being selectable as shown in the example below.
+
+```tsx example
+// Using the same table as above
+
+```
+
+## Sorting
+
+TableView supports sorting its data when a column header is pressed. To designate that a Column should support sorting, provide it with
+the `allowsSorting` prop. The TableView accepts a `sortDescriptor` prop that defines the current column key to sort by and the sort direction (ascending/descending).
+When the user presses a sortable column header, the column's key and sort direction is passed into the `onSortChange` callback, allowing you to update the `sortDescriptor` appropriately.
+
+This example performs client side sorting by passing a `sort` function to the [useAsyncList](../react-stately/useAsyncList.html) hook.
+See the docs for more information on how to perform server side sorting.
+
+```tsx example
+function AsyncSortTable() {
+ let list = useAsyncList({
+ async load({signal}) {
+ let res = await fetch(`https://swapi.dev/api/people/?search`, {signal});
+ let json = await res.json();
+ return {
+ items: json.results
+ };
+ },
+ async sort({items, sortDescriptor}) {
+ return {
+ items: items.sort((a, b) => {
+ let first = a[sortDescriptor.column];
+ let second = b[sortDescriptor.column];
+ let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
+ if (sortDescriptor.direction === 'descending') {
+ cmp *= -1;
+ }
+ return cmp;
+ })
+ };
+ }
+ });
+
+ return (
+
+
+ Name
+ Height
+ Mass
+ Birth Year
+
+
+ {item => (
+
+ {columnKey => {item[columnKey]}}
+
+ )}
+
+
+ );
+}
+```
+
+## Props
+
+### TableView props
+
+
+
+### TableHeader props
+
+
+
+### Column props
+
+
+
+### TableBody props
+
+
+
+### Row props
+
+
+
+### Cell props
+
+
+
+## Visual options
+### Column alignment
+[View guidelines](https://spectrum.adobe.com/page/table/#Usage-guidelines)
+
+```tsx example
+
+
+ Name
+ Type
+ Size
+
+
+
+ 2021406_Proposal
+ PDF
+ 86 KB
+
+
+ Budget Template
+ XLS
+ 120 KB
+
+
+ Onboarding
+ PPT
+ 472 KB
+
+
+ Welcome
+ TXT
+ 24 KB
+
+
+
+```
+
+### Column widths
+Columns support three different width props: `minWidth`, `width`, and `maxWidth`. Each of these props accepts fixed values or percentages. TableView prioritizes columns with defined widths and divides the remaining space evenly amongst the other columns.
+
+```tsx example
+
+
+ Name
+ Type
+ Size
+
+
+
+ 2021406_Proposal
+ PDF
+ 86 KB
+
+
+ Budget Template
+ XLS
+ 120 KB
+
+
+ Onboarding
+ PPT
+ 472 KB
+
+
+ Welcome
+ TXT
+ 24 KB
+
+
+
+```
+
+### Column dividers
+[View guidelines](https://spectrum.adobe.com/page/table/#Column-dividers)
+
+```tsx example
+
+
+ Name
+ Type
+ Size
+
+
+
+ 2021406_Proposal
+ PDF
+ 86 KB
+
+
+ Budget Template
+ XLS
+ 120 KB
+
+
+ Onboarding
+ PPT
+ 472 KB
+
+
+ Welcome
+ TXT
+ 24 KB
+
+
+
+```
+
+### Hide header
+Individual column headers can be hidden by providing the `hideHeader` prop to the Column. A tooltip is rendered when the column header is focused
+to compensate for the lack of a visual title. Note that the `hideHeader` prop is specifically intended for columns that contain ActionButtons instead
+of text content.
+
+```tsx example export=true
+function TableExample(props) {
+ let columns = [
+ {name: 'First Name', key: 'firstName'},
+ {name: 'Last Name', key: 'lastName'},
+ {name: 'Add Info', key: 'addInfo'},
+ {name: 'Age', key: 'age'}
+ ];
+
+ let rows = [
+ {id: '1', firstName: 'John', lastName: 'Doe', age: '45'},
+ {id: '2', firstName: 'Jane', lastName: 'Doe', age: '37'},
+ {id: '3', firstName: 'Joe', lastName: 'Schmoe', age: '67'},
+ {id: '4', firstName: 'Joe', lastName: 'Bloggs', age: '12'},
+ {id: '5', firstName: 'Taylor', lastName: 'Rodriguez Lloyd-Atkinson', age: '83'}
+ ];
+
+ return (
+
+
+ {column => (
+
+ {column.name}
+
+ )}
+
+
+ {item =>
+ (
+ {key =>
+ key === 'addInfo'
+ ?
+ : {item[key]}
+ }
+ )
+ }
+
+
+ );
+}
+```
+
+### Quiet
+[View guidelines](https://spectrum.adobe.com/page/table/#Standard-or-quiet)
+
+```tsx example
+// Using same setup as hide header example
+
+```
+
+### Density
+The amount of vertical padding that each row contains can be modified by providing the `density` prop.
+
+```tsx example
+// Using same setup as hide header example
+
+
+
+
+```
+
+### Overflow mode
+By default, text content that overflows its table cell will be truncated. You can have it wrap instead by passing `overflowMode="wrap"`
+to the TableView.
+
+```tsx example
+// Using same setup as hide header example
+
+```
+
+### Empty state
+Use the `renderEmptyState` prop to customize what the TableView will display if there are no rows provided.
+
+```tsx example
+import {Content} from '@react-spectrum/view';
+import {IllustratedMessage} from '@react-spectrum/illustratedmessage';
+import NotFound from '@spectrum-icons/illustrations/NotFound';
+import {Heading} from '@react-spectrum/text';
+
+function renderEmptyState() {
+ return (
+
+
+ No results
+ No results found
+
+ );
+}
+
+
+
+ Name
+ Type
+ Size
+
+
+ {[]}
+
+
+```
+
+### Nested columns
+
+TableView supports nesting columns, allowing you to create column groups, or "tiered" column headers. Data for the leaf columns appears in each row of the table body.
+
+```tsx example
+
+
+
+ First Name
+ Last Name
+
+
+ Age
+ Birthday
+
+
+
+
+ Sam
+ Smith
+ 36
+ May 3
+
+
+ Julia
+ Jones
+ 24
+ February 10
+
+
+ Peter
+ Parker
+ 28
+ September 7
+
+
+ Bruce
+ Wayne
+ 32
+ December 18
+
+
+
+```
+
+Nested columns can also be defined dynamically using the function syntax and the `childColumns` prop.
+The following example is the same as the example above, but defined dynamically.
+
+```tsx example
+let columns = [
+ {name: 'Name', key: 'name', children: [
+ {name: 'First Name', key: 'first', isRowHeader: true},
+ {name: 'Last Name', key: 'last', isRowHeader: true}
+ ]},
+ {name: 'Information', key: 'info', children: [
+ {name: 'Age', key: 'age'},
+ {name: 'Birthday', key: 'birthday'}
+ ]}
+];
+
+let rows = [
+ {id: 1, first: 'Sam', last: 'Smith', age: 36, birthday: 'May 3'},
+ {id: 2, first: 'Julia', last: 'Jones', age: 24, birthday: 'February 10'},
+ {id: 3, first: 'Peter', last: 'Parker', age: 28, birthday: 'September 7'},
+ {id: 4, first: 'Bruce', last: 'Wayne', age: 32, birthday: 'December 18'}
+];
+
+
+
+ {column => (
+
+ {column.name}
+
+ )}
+
+
+ {item => (
+
+ {columnKey => {item[columnKey]}}
+
+ )}
+
+
+```
diff --git a/packages/@react-stately/table/docs/useTableState.mdx b/packages/@react-stately/table/docs/useTableState.mdx
new file mode 100644
index 00000000000..eb586fc2250
--- /dev/null
+++ b/packages/@react-stately/table/docs/useTableState.mdx
@@ -0,0 +1,46 @@
+
+
+import {Layout} from '@react-spectrum/docs';
+export default Layout;
+
+import docs from 'docs:@react-stately/table';
+import {ClassAPI, HeaderInfo, FunctionAPI} from '@react-spectrum/docs';
+import packageData from '@react-stately/table/package.json';
+
+---
+category: Collections
+keywords: [table, state, grid]
+---
+
+# useTableState
+
+
{docs.exports.useTableState.description}
+
+
+
+## API
+
+
+
+
+
+
+
+
+## Interface
+
+
+
+## Example
+
+See the docs for [useTable](/react-aria/useTable.html) in react-aria for an example of `useTableState`, `Cell`, `Column`,
+`Row`, `TableBody`, and `TableHeader`.
diff --git a/packages/dev/parcel-transformer-mdx-docs/MDXTransformer.js b/packages/dev/parcel-transformer-mdx-docs/MDXTransformer.js
index 84526ee9771..0e32c313d9f 100644
--- a/packages/dev/parcel-transformer-mdx-docs/MDXTransformer.js
+++ b/packages/dev/parcel-transformer-mdx-docs/MDXTransformer.js
@@ -65,11 +65,13 @@ module.exports = new Transformer({
provider = 'ExampleThemeSwitcher';
}
- if (/^\s*function (.|\n)*}\s*$/.test(code)) {
- let name = code.match(/^\s*function (.*?)\s*\(/)[1];
- code = `${code}\nReactDOM.render(<${provider}><${name} />${provider}>, document.getElementById("${id}"));`;
- } else if (/^<(.|\n)*>$/m.test(code)) {
- code = code.replace(/^(<(.|\n)*>)$/m, `ReactDOM.render(<${provider}>$1${provider}>, document.getElementById("${id}"));`);
+ if (!options.includes('render=false')) {
+ if (/^\s*function (.|\n)*}\s*$/.test(code)) {
+ let name = code.match(/^\s*function (.*?)\s*\(/)[1];
+ code = `${code}\nReactDOM.render(<${provider}><${name} />${provider}>, document.getElementById("${id}"));`;
+ } else if (/^<(.|\n)*>$/m.test(code)) {
+ code = code.replace(/^(<(.|\n)*>)$/m, `ReactDOM.render(<${provider}>$1${provider}>, document.getElementById("${id}"));`);
+ }
}
if (!options.includes('export=true')) {
@@ -78,6 +80,11 @@ module.exports = new Transformer({
exampleCode.push(code);
+ if (options.includes('render=false')) {
+ node.meta = null;
+ return transformExample(node, preRelease);
+ }
+
if (meta === 'snippet') {
node.meta = null;
return [