Correct patterns
Updated: Sep 24, 2026
Shell and route structure
- Render every surface inside one
App. It loads the styles and provides the application-level focus, toast, and portal owners. - Size
html, body, and the mount node to the full viewport, and use var(--uit-color-background-window) for their background. - Choose either
Page or SubNavigationPager as the route root. A route must not combine both because each owns the route-level structure. - Configure the page header through
Page’s header* props so Page can coordinate the header and content inset. - Give each route one scroll owner per axis: prefer
VerticalList for rows and ScrollView for other content. This keeps directional navigation predictable. - Pass
insetForHeader to the scroll owner when content moves beneath the header. - Animate route changes through
PageTransition or the React Router integration so focus, page state, and motion remain coordinated.
- Use
ListItem for rows. Let it own showSwitch, showRadioButton, or showSlider through checked / onCheckedChange and sliderValue / onSliderValueChange, keeping each row a single accessible interaction. - Dock horizontal actions below the scroller in a
ButtonRail or ButtonGroup, with var(--uit-spacing-xsmall) bottom clearance. - Place free-floating text on a
Panel or StaticContainer to preserve legibility on the additive-light display. - Use
Modal for structured decision content within a route, and use a dedicated route or separate presenter when that decision must block the application. Use Toast.show(...) for transient outcome feedback. - Present a tooltip from its interactable with
tooltipText and tooltipMode. Anchor VerticalMenu with getVerticalMenuAnchorProps so the overlay tracks its owner. - Provide meaningful
alt text for Avatar; an ariaLabel for ScrollView, VerticalList, Pager, and SubNavigationPager; and a localized readMoreLabel for ReadMoreTextView.
- Use semantic tokens for color, spacing, corner radius, size, and elevation. Use exported motion constants for durations because durations are not theme tokens.
- Apply typography through
textStyle={TextStyle.*} on TextView or the matching TextAppearanceuit-text-* class on an application element. - Define motion with
AnimationDurations.*, Interpolators.*, SpringConfigs.*, and createTransition(...) to keep timing consistent. - Honor reduced-motion preferences with
usePrefersReducedMotion() and reducedMotionDuration(...). - Pass icons as an
IconSource: inline vector data, a { uri, tinted } object, or a bare URL string. Continue using dedicated URL props such as src, avatarSrc, and bannerSrc for photographic and brand imagery. - Let components own smooth-corner geometry, tails, and shape paths.
- Derive layout from the available viewport so it responds across display sizes.
- Validate default, focused, pressed, disabled, selected, loading, popup, and scroll states because each can change user-visible rendering or behavior.
- Avoid building toolkit-looking rows, chips, tags, buttons, or headers from
div elements or from a different toolkit component. Use the matching semantic component so its accessibility and interaction behavior are included. - Do not render an application-owned
ToastContainer; App already presents toasts. - Do not duplicate the
FocusNavigationProvider or FloatingPortalRootProvider supplied by App. Nest one only when a separate container needs its own focus scope or an embedding host owns the overlay layer. - Keep
Header under Page ownership. Do not render it independently or next to SubNavigationPager. - Avoid placing
VerticalList or ListItem inside ScrollView, and avoid a separate list for each section. One scroll owner provides unambiguous focus movement. - Keep ordinary
ListItem rows free of application-added margins, gaps, dividers, and chevrons that only imply navigation. - Use
insetForHeader rather than application padding to clear the page header. - Use router history and the Back/Escape path instead of adding an in-app back button.
- Let
Button, Chip, Tag, AppBadge, Header, and ListItem size themselves from content rather than forcing dimensions onto them. - Avoid nesting
Container, StaticContainer, Surface, Panel, Button, Chip, Tag, or Header inside another member of that set. Each owns a complete surface. - A component that is one interaction target must not contain focusable children, and the interface must not show two focused elements at once. These constraints keep focus semantics unambiguous.
- Treat
Switch and RadioButton as presentational. Drive them from ListItem or another interactable that owns activation. - Prefer a semantic component over a foundation container. If the toolkit has no matching component, build from the supported foundation APIs rather than imitating one with custom markup.
- Avoid vertical stacks of primary buttons, non-button children in
ButtonRail, and rails used in place of SubNavigationPager. - Place a rail or group with its related content actions, not below the header or at the top of a list.
- Avoid hardcoded colors, pixel spacing, radii, font sizes, durations, and copied literal fallbacks for tokens. Semantic tokens and motion constants preserve theme consistency.
- Let the component own smooth-corner paths, tails, and cutouts rather than applying a raw
border-radius. - Do not override material internals, target generated class names, or select internal nodes from application CSS or DOM queries. Internal structure is not a public API.
- Keep
style focused on layout instead of repainting the toolkit-owned visuals. - Preserve avatar badge cutouts rather than imitating them with painted backgrounds.
- Restore the component from its pressed state after activation so its visual state matches the interaction state.
- Let
Header animate icon, avatar, and loading changes; application animation would compete with component-owned transitions. - Keep focus stable during loading unless the flow requires a new target.
- Every focus target must perform a meaningful action. A toast-only action, no-op callback, or placeholder creates an unusable navigation stop.
import { ListItem, Page, VerticalList } from '@wearables-ui-toolkit/mrbd';
function MessageList({ messages }: { messages: Message[] }) {
return (
<Page headerText="Messages">
<VerticalList insetForHeader ariaLabel="Messages">
{messages.map(message => (
<ListItem
key={message.id}
title={message.senderName}
subtitle={message.preview}
avatarSrc={message.avatarUrl}
avatarAlt={message.senderName}
timestamp={message.timestamp}
onClick={() => openThread(message.id)}
/>
))}
</VerticalList>
</Page>
);
}
This composition gives each behavior one owner: Page manages the header and system-bar inset, VerticalList manages scrolling, fading edges, the scrollbar, and scroll-to-focused behavior, and ListItem manages row anatomy, material, focus and press state, spacing, and typography.
function BadMessageList({ messages }: { messages: Message[] }) {
return (
<div className="custom-page">
<h1>Messages</h1>
{messages.map(message => (
<div
key={message.id}
className="custom-row"
onClick={() => openThread(message.id)}
>
{message.senderName}
</div>
))}
</div>
);
}
This implementation has several user-visible problems:
- It bypasses
App, Page, VerticalList, and ListItem. - It requires custom color, typography, shape, and spacing to imitate an existing component.
- It provides no focus eligibility, focus material, press state, rubber-band feedback, scroll-to-focused behavior, or accessible row semantics.
- It cannot restore focus or scroll position after back navigation.