@xsolla/xui-status-dropdown
v0.209.1
Published
A compact interactive status tag that opens a menu to change the current status. Cross-platform (web and native).
Readme
StatusDropdown
A compact interactive status tag that opens a menu to change the current status. Cross-platform (web and native).
A compact inline tag-trigger that displays the current status of a record and opens a ContextMenu for changing it. Combines a coloured dot, a status label, and a chevron into a single small interactive element. Designed for use inside table rows, list items, and detail panels where inline status editing is required without navigating away.
When to use
- In data tables where each row has a status that users can update directly (e.g. order status, moderation state, task progress)
- In detail panels or forms where a status field must be editable inline without a modal
- When the list of statuses is predefined and finite, and selecting a new one immediately applies a change
- When the available status palette carries semantic meaning (success, warning, alert) that should be communicated through colour
When not to use
- When the status is read-only — use Status or Tag component instead
- When there are more than ~7–8 status options, or when options require descriptions longer than a single line — consider a Select or a dedicated side panel
- When the status change triggers a complex confirmation flow — use a full Dialog rather than an inline dropdown
- As a navigation element — StatusDropdown changes a value, it does not navigate
Content guidelines
- Status labels — keep labels short: one or two words maximum. Examples: "Active", "Pending", "Rejected", "In review", "Draft". Avoid verbose labels like "Currently under moderation review".
- Description lines — use the optional ContextMenu description line to clarify what a status means or what happens when it is selected. Example: label "Archived", description "Hidden from all users. Can be restored."
- Palette assignment — assign palettes semantically and consistently across the product. Do not use Alert (red) for a neutral state. Do not use Success (green) for a warning state. Document the palette-to-status mapping in the product's status glossary.
- Number of options — keep the ContextMenu list to 7 options or fewer. If more are needed, consider grouping them or using a different pattern.
Behaviour guidelines
- Opening — clicking or pressing Enter / Space on the trigger opens the ContextMenu. The trigger switches to Open=True state and the chevron rotates.
- Selection — clicking a ContextMenu item immediately applies the new status: the trigger label and dot update to reflect the selection, the menu closes, and the change is committed. There is no separate confirm step.
- Current selection — the currently active status is shown with a checked Checkbox in the menu and should not trigger a change if re-selected. Clicking the currently active item closes the menu without any state change.
- Closing — the ContextMenu closes on: selecting an item, clicking outside the menu, pressing Escape, or pressing Tab past the last item.
- Optimistic update — update the trigger UI immediately on selection and send the change to the backend in the background. If the request fails, revert the trigger to the previous status and surface an error (e.g. a Toast notification).
- Loading state — if the status change requires an async operation that takes noticeable time, show a spinner in the context-menu while the request is in flight.
- Width — the trigger width is determined by the current label text plus dot and chevron. It is not fixed — expect it to change width when the status label changes. Ensure the surrounding layout accommodates this (e.g. avoid placing adjacent elements at a fixed offset from the trigger's right edge).
- Placement in tables — in a table cell, left-align the trigger. Do not stretch it to fill the cell width unless the table column width is narrow enough that all options need the full width.
Accessibility
- The trigger must have role="button" with aria-haspopup="listbox" and aria-expanded set to true when open and false when closed.
- Provide aria-label on the trigger that includes both the field name and the current value — e.g. aria-label="Status: Active". Do not rely on the visual dot colour alone.
- The ContextMenu list must have role="listbox" with aria-label="Change status".
- Each option must have role="option" with aria-selected="true" for the current selection and aria-selected="false" for all others.
- Keyboard navigation inside the open menu: ↑ / ↓ moves between options; Enter / Space selects; Escape closes without selecting.
- When the menu closes after a selection, return focus to the trigger.
- Do not rely on the Palette colour alone to convey the status — always pair it with the text label. This satisfies WCAG 1.4.1 (Use of Colour).
- If Dot=False, the chevron remains the only visual affordance that the trigger is interactive — ensure it is clearly visible against the background at all palette variants.
Installation
npm install @xsolla/xui-status-dropdownImports
import { StatusDropdown } from '@xsolla/xui-status-dropdown';
import type { StatusDropdownProps, StatusDropdownPalette } from '@xsolla/xui-status-dropdown';
import { ContextMenuItem } from '@xsolla/xui-context-menu';Also re-exported from the @xsolla/xui-controls meta-package:
import { StatusDropdown } from '@xsolla/xui-controls';Quick start
const [status, setStatus] = useState('Success');
<StatusDropdown label={status} palette="Success" dot>
<ContextMenuItem onPress={() => setStatus('Success')}>Success</ContextMenuItem>
<ContextMenuItem onPress={() => setStatus('Warning')}>Warning</ContextMenuItem>
<ContextMenuItem onPress={() => setStatus('Alert')}>Alert</ContextMenuItem>
</StatusDropdown>;API Reference
<StatusDropdown>
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| label | string | — | Current status text shown on the trigger. |
| palette | 'Neutral' \| 'Success' \| 'Warning' \| 'Alert' \| 'Brand' | 'Neutral' | Colour palette for the trigger. |
| dot | boolean | false | Show a small status dot before the label. |
| children | ReactNode | — | Menu content; typically ContextMenuItem elements. |
| placement | ContextMenuPlacement | 'bottom-start' | Menu placement relative to the trigger. |
| onSelect | (value: any) => void | — | Fired when a menu item is selected. |
| isOpen | boolean | — | Controlled open state. |
| onOpenChange | (isOpen: boolean) => void | — | Fires when the open state changes. |
| aria-label | string | `Status: ${label}` | Accessible label for the trigger. |
| id | string | — | HTML id on the underlying dropdown. |
| testID | string | — | Test identifier; suffixed for inner trigger and dot. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
Types
type StatusDropdownPalette = 'Neutral' | 'Success' | 'Warning' | 'Alert' | 'Brand';Examples
Controlled selection
Item-level onPress handlers on each ContextMenuItem drive the controlled value; this is the recommended pattern.
const STATUSES = [
{ label: 'Success', palette: 'Success' },
{ label: 'Warning', palette: 'Warning' },
{ label: 'Alert', palette: 'Alert' },
{ label: 'Neutral', palette: 'Neutral' },
{ label: 'Brand', palette: 'Brand' },
] as const;
const [current, setCurrent] = useState(STATUSES[0]);
<StatusDropdown label={current.label} palette={current.palette} dot>
{STATUSES.map((s) => (
<ContextMenuItem key={s.label} onPress={() => setCurrent(s)}>
{s.label}
</ContextMenuItem>
))}
</StatusDropdown>Without dot
<StatusDropdown label="Warning" palette="Warning">
<ContextMenuItem>Success</ContextMenuItem>
<ContextMenuItem>Warning</ContextMenuItem>
</StatusDropdown>Accessibility
- Trigger renders with
role="button"andaria-haspopup="listbox". - An
aria-labelis auto-generated fromlabelwhen not supplied. - Menu open state is communicated through the underlying
Dropdown; chevron rotates with the open state.
