Shared TypeScript type definitions for React Spectrum components and hooks, providing common interfaces for DOM interactions, styling, accessibility, internationalization, and component behavior across the React Spectrum ecosystem
—
Generic collection interfaces supporting lists, grids, trees, and other data structures with keyboard navigation, sorting, expansion, loading states, and layout management.
The fundamental collection interface that provides iteration and key-based access to items.
/**
* A generic interface to access a readonly sequential collection of unique keyed items
* @template T The type of items in the collection
*/
interface Collection<T> extends Iterable<T> {
/** The number of items in the collection */
readonly size: number;
/** Iterate over all keys in the collection */
getKeys(): Iterable<Key>;
/** Get an item by its key */
getItem(key: Key): T | null;
/** Get an item by the index of its key */
at(idx: number): T | null;
/** Get the key that comes before the given key in the collection */
getKeyBefore(key: Key): Key | null;
/** Get the key that comes after the given key in the collection */
getKeyAfter(key: Key): Key | null;
/** Get the first key in the collection */
getFirstKey(): Key | null;
/** Get the last key in the collection */
getLastKey(): Key | null;
/** Iterate over the child items of the given key */
getChildren?(key: Key): Iterable<T>;
/** Returns a string representation of the item's contents */
getTextValue?(key: Key): string;
/** Filters the collection using the given function */
filter?(filterFn: (nodeValue: string, node: T) => boolean): Collection<T>;
}Nodes represent individual items within a collection with metadata and hierarchy support.
/**
* Represents a node in a collection
* @template T The type of the node's value
*/
interface Node<T> {
/** The type of item this node represents */
type: string;
/** A unique key for the node */
key: Key;
/** The object value the node was created from */
value: T | null;
/** The level of depth this node is at in the hierarchy */
level: number;
/** Whether this item has children, even if not loaded yet */
hasChildNodes: boolean;
/**
* The loaded children of this node
* @deprecated Use collection.getChildren(node.key) instead
*/
childNodes: Iterable<Node<T>>;
/** The rendered contents of this node (e.g. JSX) */
rendered: ReactNode;
/** A string value for this node, used for features like typeahead */
textValue: string;
/** An accessibility label for this node */
"aria-label"?: string;
/** The index of this node within its parent */
index: number;
/** A function that should be called to wrap the rendered node */
wrapper?: (element: ReactElement) => ReactElement;
/** The key of the parent node */
parentKey?: Key | null;
/** The key of the node before this node */
prevKey?: Key | null;
/** The key of the node after this node */
nextKey?: Key | null;
/** Additional properties specific to a particular node type */
props?: any;
/** @private */
shouldInvalidate?: (context: any) => boolean;
/** A function that renders this node to a React Element in the DOM */
render?: (node: Node<any>) => ReactElement;
}Building blocks for creating collection content with React elements.
/**
* Properties for collection items
* @template T The type of the item data
*/
interface ItemProps<T> extends LinkDOMProps {
/** Rendered contents of the item or child items */
children: ReactNode;
/** Rendered contents of the item if children contains child items */
title?: ReactNode;
/** A string representation of the item's contents, used for features like typeahead */
textValue?: string;
/** An accessibility label for this item */
"aria-label"?: string;
/** A list of child item objects. Used for dynamic collections */
childItems?: Iterable<T>;
/** Whether this item has children, even if not loaded yet */
hasChildItems?: boolean;
}
/**
* Properties for collection sections
* @template T The type of the item data
*/
interface SectionProps<T> {
/** Rendered contents of the section, e.g. a header */
title?: ReactNode;
/** An accessibility label for the section */
"aria-label"?: string;
/** Static child items or a function to render children */
children: ItemElement<T> | ItemElement<T>[] | ItemRenderer<T>;
/** Item objects in the section */
items?: Iterable<T>;
}
/** React element representing an item */
type ItemElement<T> = ReactElement<ItemProps<T>> | null;
/** Function that renders an item */
type ItemRenderer<T> = (item: T) => ItemElement<T>;
/** React element representing a section */
type SectionElement<T> = ReactElement<SectionProps<T>> | null;
/** Union of collection elements */
type CollectionElement<T> = SectionElement<T> | ItemElement<T>;
/** Children content for collections */
type CollectionChildren<T> = CollectionElement<T> | CollectionElement<T>[] | ((item: T) => CollectionElement<T>);Base properties for collections with static and dynamic content support.
/**
* Base properties for collections
* @template T The type of items in the collection
*/
interface CollectionBase<T> {
/** The contents of the collection */
children: CollectionChildren<T>;
/** Item objects in the collection */
items?: Iterable<T>;
/** The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with */
disabledKeys?: Iterable<Key>;
}
/**
* Base properties for collection state
* @template T The type of items in the collection
* @template C The type of the collection
*/
interface CollectionStateBase<T, C extends Collection<Node<T>> = Collection<Node<T>>> extends Partial<CollectionBase<T>> {
/** A pre-constructed collection to use instead of building one from items and children */
collection?: C;
}Support for asynchronous collections with loading indicators.
/** Loading states for collections */
type LoadingState = "loading" | "sorting" | "loadingMore" | "error" | "idle" | "filtering";
/**
* Properties for collections that support async loading
*/
interface AsyncLoadable {
/** Whether the items are currently loading */
isLoading?: boolean;
/** Handler that is called when more items should be loaded, e.g. while scrolling near the bottom */
onLoadMore?: () => any;
}Support for hierarchical collections with expand/collapse functionality.
/**
* Properties for expandable collections (trees)
*/
interface Expandable {
/** The currently expanded keys in the collection (controlled) */
expandedKeys?: Iterable<Key>;
/** The initial expanded keys in the collection (uncontrolled) */
defaultExpandedKeys?: Iterable<Key>;
/** Handler that is called when items are expanded or collapsed */
onExpandedChange?: (keys: Set<Key>) => any;
}Support for collections with sorting functionality.
/** Sort direction options */
type SortDirection = "ascending" | "descending";
/**
* Sort descriptor defining how to sort a collection
*/
interface SortDescriptor {
/** The key of the column to sort by */
column: Key;
/** The direction to sort by */
direction: SortDirection;
}
/**
* Properties for sortable collections
*/
interface Sortable {
/** The current sorted column and direction */
sortDescriptor?: SortDescriptor;
/** Handler that is called when the sorted column or direction changes */
onSortChange?: (descriptor: SortDescriptor) => any;
}Interfaces for custom keyboard navigation within collections.
/**
* Keyboard navigation delegate for custom key handling
*/
interface KeyboardDelegate {
/** Returns the key visually below the given one, or null for none */
getKeyBelow?(key: Key): Key | null;
/** Returns the key visually above the given one, or null for none */
getKeyAbove?(key: Key): Key | null;
/** Returns the key visually to the left of the given one, or null for none */
getKeyLeftOf?(key: Key): Key | null;
/** Returns the key visually to the right of the given one, or null for none */
getKeyRightOf?(key: Key): Key | null;
/** Returns the key visually one page below the given one, or null for none */
getKeyPageBelow?(key: Key): Key | null;
/** Returns the key visually one page above the given one, or null for none */
getKeyPageAbove?(key: Key): Key | null;
/** Returns the first key, or null for none */
getFirstKey?(key?: Key | null, global?: boolean): Key | null;
/** Returns the last key, or null for none */
getLastKey?(key?: Key | null, global?: boolean): Key | null;
/** Returns the next key after fromKey that matches the given search string, or null for none */
getKeyForSearch?(search: string, fromKey?: Key | null): Key | null;
}Interfaces for managing collection layout and virtualization.
/**
* Rectangle type for layout calculations
*/
interface Rect {
x: number;
y: number;
width: number;
height: number;
}
/**
* Size type for dimensions
*/
interface Size {
width: number;
height: number;
}
/**
* Layout delegate provides layout information for collection items
*/
interface LayoutDelegate {
/** Returns a rectangle for the item with the given key */
getItemRect(key: Key): Rect | null;
/** Returns the visible rectangle of the collection */
getVisibleRect(): Rect;
/** Returns the size of the scrollable content in the collection */
getContentSize(): Size;
/** Returns a list of keys between from and to */
getKeyRange?(from: Key, to: Key): Key[];
}Usage Examples:
import {
Collection,
Node,
CollectionBase,
ItemProps,
SectionProps,
Expandable,
Sortable,
AsyncLoadable,
SortDescriptor,
Key
} from "@react-types/shared";
// Basic list component with collection support
interface ListProps<T> extends CollectionBase<T>, AsyncLoadable {
onAction?: (key: Key) => void;
}
function List<T>({
children,
items,
disabledKeys,
isLoading,
onLoadMore,
onAction
}: ListProps<T>) {
// In a real implementation, you'd build a collection from children/items
// This is a simplified example showing the interface usage
return (
<div role="list">
{isLoading && <div>Loading...</div>}
{/* Render collection items */}
<div>Collection items would be rendered here</div>
{onLoadMore && (
<button onClick={onLoadMore}>Load More</button>
)}
</div>
);
}
// Tree component with expansion support
interface TreeProps<T> extends CollectionBase<T>, Expandable {
onAction?: (key: Key) => void;
}
function Tree<T>({
children,
items,
disabledKeys,
expandedKeys,
defaultExpandedKeys,
onExpandedChange,
onAction
}: TreeProps<T>) {
const [expanded, setExpanded] = useState<Set<Key>>(
new Set(defaultExpandedKeys || [])
);
const handleToggleExpanded = (key: Key) => {
const newExpanded = new Set(expandedKeys || expanded);
if (newExpanded.has(key)) {
newExpanded.delete(key);
} else {
newExpanded.add(key);
}
setExpanded(newExpanded);
onExpandedChange?.(newExpanded);
};
return (
<div role="tree">
{/* Tree implementation would render nodes with expand/collapse controls */}
<div>Tree nodes would be rendered here</div>
</div>
);
}
// Table component with sorting support
interface TableProps<T> extends CollectionBase<T>, Sortable {
columns: Array<{
key: Key;
name: string;
allowsSorting?: boolean;
}>;
}
function Table<T>({
children,
items,
columns,
sortDescriptor,
onSortChange
}: TableProps<T>) {
const handleSort = (column: Key) => {
const newDescriptor: SortDescriptor = {
column,
direction:
sortDescriptor?.column === column && sortDescriptor.direction === "ascending"
? "descending"
: "ascending"
};
onSortChange?.(newDescriptor);
};
return (
<table>
<thead>
<tr>
{columns.map(column => (
<th
key={column.key}
onClick={() => column.allowsSorting && handleSort(column.key)}
style={{
cursor: column.allowsSorting ? "pointer" : "default"
}}
>
{column.name}
{sortDescriptor?.column === column.key && (
<span>{sortDescriptor.direction === "ascending" ? " ↑" : " ↓"}</span>
)}
</th>
))}
</tr>
</thead>
<tbody>
{/* Table rows would be rendered here */}
</tbody>
</table>
);
}
// Usage with static content
function StaticListExample() {
return (
<List>
<Item key="1">First item</Item>
<Item key="2">Second item</Item>
<Section title="Group A">
<Item key="3">Third item</Item>
<Item key="4">Fourth item</Item>
</Section>
</List>
);
}
// Usage with dynamic content
function DynamicListExample() {
const items = [
{ id: 1, name: "Apple", category: "Fruit" },
{ id: 2, name: "Carrot", category: "Vegetable" },
{ id: 3, name: "Banana", category: "Fruit" }
];
return (
<List items={items} disabledKeys={[2]}>
{(item) => (
<Item key={item.id} textValue={item.name}>
{item.name} ({item.category})
</Item>
)}
</List>
);
}
// Tree with expansion
function TreeExample() {
const treeData = [
{
id: 1,
name: "Documents",
children: [
{ id: 2, name: "Resume.pdf" },
{ id: 3, name: "Cover Letter.doc" }
]
},
{
id: 4,
name: "Images",
children: [
{ id: 5, name: "Photo1.jpg" },
{ id: 6, name: "Photo2.png" }
]
}
];
return (
<Tree
items={treeData}
defaultExpandedKeys={[1]}
onExpandedChange={(keys) => console.log("Expanded keys:", keys)}
>
{(item) => (
<Item
key={item.id}
hasChildItems={!!item.children?.length}
childItems={item.children}
>
{item.name}
</Item>
)}
</Tree>
);
}
// Sortable table
function SortableTableExample() {
const data = [
{ id: 1, name: "Alice", age: 30, department: "Engineering" },
{ id: 2, name: "Bob", age: 25, department: "Design" },
{ id: 3, name: "Carol", age: 35, department: "Marketing" }
];
const columns = [
{ key: "name", name: "Name", allowsSorting: true },
{ key: "age", name: "Age", allowsSorting: true },
{ key: "department", name: "Department", allowsSorting: false }
];
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "name",
direction: "ascending"
});
return (
<Table
items={data}
columns={columns}
sortDescriptor={sortDescriptor}
onSortChange={setSortDescriptor}
>
{(item) => (
<Row key={item.id}>
<Cell>{item.name}</Cell>
<Cell>{item.age}</Cell>
<Cell>{item.department}</Cell>
</Row>
)}
</Table>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-types--shared