@xsolla/xui-cell
v0.222.0
Published
A cross-platform layout primitive for list rows and card-like surfaces, with leading/trailing slots, a flexible content area, and an opinionated multi-line text block.
Readme
Cell
A cross-platform layout primitive for list rows and card-like surfaces, with leading/trailing slots, a flexible content area, and an opinionated multi-line text block.
Installation
npm install @xsolla/xui-cellImports
import { Cell } from "@xsolla/xui-cell";Quick start
import * as React from "react";
import { Cell } from "@xsolla/xui-cell";
export default function QuickStart() {
return (
<Cell>
<Cell.Content>Basic cell content</Cell.Content>
</Cell>
);
}API Reference
<Cell>
Container row. Extends BoxProps, so any layout/style prop the underlying Box accepts is forwarded.
| Prop | Type | Default | Description |
| ----------- | ------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
| children | ReactNode | - | Slots, content, or text block. |
| view | "default" \| "stroke" \| "surface" | "default" | Visual variant. default is a plain row (min height 56px). stroke adds a 1px border, 8px radius and 8px padding. surface adds a secondary background fill, 8px radius and 8px padding. |
| withBoard | boolean | false | Deprecated. When true and view is unset, behaves like view="stroke". |
| onPress | () => void | - | Press/click handler. When set, the cell becomes interactive and shows a pointer cursor. |
| disabled | boolean | false | Marks the row as non-actionable — suppresses hover/press styles, ignores onPress, shows cursor: not-allowed, sets aria-disabled. Does not dim the row. See below. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
Spacing
The bounded views (stroke, surface) pad their content by 8px; default is unpadded and instead reserves a 56px minimum height. All views lay their slots out in a row with a 12px gap.
Hover / active state
Every view responds to pointer hover and press by laying the overlay.mono token over its resting fill:
| View | Resting fill | Hover / active fill |
| --------- | ---------------------- | ------------------------------------------- |
| default | none | overlay.mono |
| stroke | none (1px border) | overlay.mono |
| surface | background.secondary | overlay.mono over background.secondary |
Because surface already paints background.secondary, its hover fill is composed of two layers so the row darkens rather than staying flat — a single background-color would replace the resting fill instead of tinting it.
Hover is not conditional on onPress: a Cell without a press handler still shows the hover fill. Only disabled turns it off.
Disabled
disabled marks a row that is present but not actionable. It:
- suppresses the hover and press fills on every view,
- ignores
onPress(the handler is never attached), - renders
cursor: not-allowed, - sets
aria-disabledon the row (an explicitaria-disabledprop still wins).
It deliberately does not do what BoxProps["disabled"] does. On web Box maps disabled to opacity: 0.5 plus pointer-events: none, which dims the row and makes interactive children inside Cell.Slot (a Radio, a Button) unclickable. Cell therefore consumes the prop itself rather than forwarding it, so:
- the row keeps its normal colours — the consumer owns the dimmed look, e.g. by passing
opacity, - controls inside the slots keep working.
import * as React from "react";
import { Cell } from "@xsolla/xui-cell";
import { Button } from "@xsolla/xui-button";
export default function DisabledCell() {
return (
<>
{/* Row is inert: no hover, no press, aria-disabled. */}
<Cell view="stroke" disabled onPress={() => console.log("never runs")}>
<Cell.Text title="Unavailable option" caption="Not selectable" />
</Cell>
{/* The row is disabled but the trailing button still works. */}
<Cell view="stroke" disabled onPress={() => console.log("never runs")}>
<Cell.Text title="Row press ignored" />
<Cell.Slot>
<Button size="sm" onPress={() => console.log("still fires")}>
Action
</Button>
</Cell.Slot>
</Cell>
{/* Opt in to the dimmed look yourself. */}
<Cell view="stroke" disabled opacity={0.5}>
<Cell.Text title="Dimmed by the consumer" />
</Cell>
</>
);
}<Cell.Slot>
Fixed-width container for leading or trailing content (icons, avatars, buttons). Does not flex.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ------------- |
| children | ReactNode | - | Slot content. |
<Cell.Content>
Flexible content area that fills the remaining space.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ----------------- |
| children | ReactNode | - | Content elements. |
<Cell.Text>
Pre-styled multi-row text block. Each row has independent left and right slots with size and weight tuned for cells.
| Prop | Type | Default | Description |
| ------------------ | ----------- | ------- | ----------------------------------------------------------------------------------------- |
| title | ReactNode | - | Primary text on the title row (left). 16px / 500 weight when string. |
| titleRight | ReactNode | - | Secondary text on the title row (right). 12px / 400 weight when string. |
| description | ReactNode | - | Description on the second row (left). 12px / 400 weight when string. |
| descriptionRight | ReactNode | - | Description on the second row (right). 12px / 400 weight when string. |
| caption | ReactNode | - | Caption on the third row (left). 12px / 400 weight when string, tertiary content colour. |
| captionRight | ReactNode | - | Caption on the third row (right). 12px / 400 weight when string, tertiary content colour. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
Anatomy
import { Cell } from "@xsolla/xui-cell";
<Cell view="default">
<Cell.Slot>{/* leading */}</Cell.Slot>
<Cell.Content>{/* flexible body */}</Cell.Content>
{/* or use Cell.Text instead of Cell.Content */}
<Cell.Text title="Title" description="Description" caption="Caption" />
<Cell.Slot>{/* trailing */}</Cell.Slot>
</Cell>;Examples
View variants
import * as React from "react";
import { Cell } from "@xsolla/xui-cell";
export default function Views() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<Cell view="default">
<Cell.Content>Default</Cell.Content>
</Cell>
<Cell view="stroke">
<Cell.Content>Stroke</Cell.Content>
</Cell>
<Cell view="surface">
<Cell.Content>Surface</Cell.Content>
</Cell>
</div>
);
}With Cell.Text
import * as React from "react";
import { Cell } from "@xsolla/xui-cell";
import { Avatar } from "@xsolla/xui-avatar";
import { ChevronRight } from "@xsolla/xui-icons-base";
export default function CellWithText() {
return (
<Cell view="stroke">
<Cell.Slot>
<Avatar size="md" name="Jane Smith" />
</Cell.Slot>
<Cell.Text
title="Jane Smith"
titleRight="Pro"
description="[email protected]"
descriptionRight="2h ago"
caption="Product Manager"
/>
<Cell.Slot>
<ChevronRight size={20} aria-hidden />
</Cell.Slot>
</Cell>
);
}Pressable cell
import * as React from "react";
import { Cell } from "@xsolla/xui-cell";
import { ChevronRight } from "@xsolla/xui-icons-base";
export default function PressableCell() {
return (
<Cell onPress={() => console.log("pressed")}>
<Cell.Text
title="Account settings"
description="Email, password, sessions"
/>
<Cell.Slot>
<ChevronRight size={20} aria-hidden />
</Cell.Slot>
</Cell>
);
}Settings list
import * as React from "react";
import { Cell } from "@xsolla/xui-cell";
import { ChevronRight } from "@xsolla/xui-icons-base";
export default function SettingsList() {
return (
<div>
<Cell onPress={() => {}}>
<Cell.Text title="Profile" />
<Cell.Slot>
<ChevronRight size={16} aria-hidden />
</Cell.Slot>
</Cell>
<Cell onPress={() => {}}>
<Cell.Text title="Notifications" />
<Cell.Slot>
<ChevronRight size={16} aria-hidden />
</Cell.Slot>
</Cell>
<Cell disabled>
<Cell.Text title="Security" caption="Managed by your organisation" />
<Cell.Slot>
<ChevronRight size={16} aria-hidden />
</Cell.Slot>
</Cell>
</div>
);
}Accessibility
- Provide an accessible label on the cell (or its leading slot) when used as an action.
- When
onPressis set, ensure the cell receives keyboard focus on web; pair it with arolesuch asbuttonif the surrounding context does not already imply interactivity. disabledsetsaria-disabledon the row. It does not remove the row from the tab order — if the cell is focusable (because you gave it atabIndexorrole), drop thetabIndexwhile it is disabled, or keep it focusable so screen-reader users can still discover it.- Decorative icons inside slots should be marked
aria-hidden.
