Patterns
Rich Selector Row
When to use:
- an option needs iconography or secondary visual context
Composition notes:
- keep the row a single selection surface
Trade-offs:
- scanable and expressive
- more complex than plain text rows
import { Icon, Option, Text } from '@vega-ui/react';
import { CopyIcon, LinkIcon, MailIcon, Share2Icon } from '@vega-ui/icons';
const actions = [
{ value: 'link', label: 'Copy link', hint: 'Anyone with the link can view', icon: LinkIcon },
{ value: 'email', label: 'Send by email', hint: 'Up to 20 recipients', icon: MailIcon },
{ value: 'duplicate', label: 'Duplicate', hint: 'Creates a private copy', icon: CopyIcon },
{ value: 'export', label: 'Export to Drive', hint: 'PDF or DOCX', icon: Share2Icon },
];
export default function RichOptionContent() {
return (Parent-Owned Selection
When to use:
- the list state lives in a custom parent control
Composition notes:
- derive
selectedand click handlers from the parent state - keep keyboard behavior on the list: it holds the focus and points at the active row with
aria-activedescendant
Trade-offs:
- flexible
- requires a real parent selection model
import { Option, Text } from '@vega-ui/react';
import { KeyboardEvent, useId, useState } from 'react';
const statuses = [
{ value: 'backlog', label: 'Backlog' },
{ value: 'progress', label: 'In progress' },
{ value: 'review', label: 'In review' },
{ value: 'done', label: 'Done' },
];
export default function SelectedStateFromParent() {
const id = useId();Disabled Business State
When to use:
- an option is visible but unavailable
Composition notes:
- keep the reason visible in the row and link it with
aria-describedby - leave the row non-interactive
Trade-offs:
- preserves discoverability
- unavailable options still need explanatory context
import { Option } from '@vega-ui/react';
import { useId } from 'react';
const plans = [
{ value: 'starter', label: 'Starter', reason: '' },
{ value: 'team', label: 'Team', reason: '' },
{ value: 'legacy', label: 'Legacy Pro', reason: 'No longer available for new workspaces' },
{ value: 'enterprise', label: 'Enterprise', reason: 'Requires a sales contract' },
];
export default function UnavailablePlanHint() {
const id = useId();Comparison
Quick Decision Rule
Use Option when building a list-like selector surface that should match VegaUI option styling. Use Button for generic actions and SelectOption when you already have a complete Select composition.
Option vs Button
- Use
Optionfor selectable list rows. - Use
Buttonfor direct actions.
Option vs SelectOption
- Use
Optionwhen building a custom selector primitive. - Use
SelectOptioninside the fullSelectsubsystem.
Choose This Component When
- you need a reusable presentational option row
- the parent pattern already owns selection logic
Do Not Choose This Component When
- you need a full selector system without extra work
- the row is really an action, not a selectable option
Troubleshooting
The Row Looks Selectable But Selection Does Not Persist
Symptom
Clicking the row changes nothing durable in the UI.
Likely Cause
Option was rendered without a parent selection model.
How To Verify
- inspect whether
selectedis driven from parent state
Fix
Move selection logic into the parent control and pass the resulting state into each option.
The Keyboard Does Not Reach The Options
Symptom
The arrows do nothing and no option looks active.
Likely Cause
Option does not take focus or handle keys, and nothing owns the list.
How To Verify
- check that a parent has the focus, sets
aria-activedescendant, and passesactiveto one option
Fix
Use Listbox or Select, which do this for you.
Disabled Rows Still Need Explanation
Symptom
Users can see a disabled option but do not know why it is unavailable.
Likely Cause
The row is disabled visually without surrounding explanatory copy.
How To Verify
- test the flow with a user who has not seen the business rule before
Fix
Keep the row disabled and surface the reason nearby through helper text or explanatory copy.