| Base | Recommended use | Toolkit responsibilities | Component responsibilities |
|---|---|---|---|
An interactive component that draws its own visuals without a material. | Focus ownership, press and long-press, activation, disabled behavior, tooltips, partial-focus handoff, polymorphic as. | The complete appearance. | |
An interactive component that needs the standard focused and pressed material. | Everything from InteractableBase, plus material rendering, shape-driven clipping, and content-scale animation between states. | Layout and content inside the container. | |
A non-interactive surface that still needs a material. | A material in a fixed visual state, smooth corners, and optional coordinated layout transitions. | Non-interactive layout and content. It has no focus or press behavior. | |
A quiet informational backdrop rather than a control. | A non-focusable, non-pressable, and non-clickable Container, with reduced-opacity panel material and no content-scale breathing. | Layout and content inside the panel. | |
A plain, semantically sized box with a named corner radius. | InteractableBase behavior and a background using a SurfaceCornerRadius case. | Content layout. | |
A plain element | Pure layout or text. | No toolkit behavior, which is appropriate for this case. | All behavior and styling; keep the element focused on one purpose. |
Button, ListItem, or Card from Container only to change its appearance. Likewise, avoid assembling primitives to imitate a component that the toolkit does not provide, because the result will not inherit that component’s expected behavior.div with additional behavior, extend UITCommonProps. It includes standard DOM attributes and typed data-uit-* keys, so the component does not need its own attribute allowlist.Container, StaticContainer, and Surface default width and height to automatic sizing; set a dimension only when the design fixes it.var(--uit-spacing-large), so the component follows system-wide visual updates. Avoid literal values for these properties.min-inline-size: 0 and min-block-size: 0 on flex and grid children so long labels can shrink instead of overflowing..stat-tile {
display: grid;
gap: var(--uit-spacing-xsmall);
padding: var(--uit-spacing-medium);
min-inline-size: 0;
}
Container’s standard interactive material. MaterialLibrary.default() builds the same material, and each MaterialLibrary entry returns a fresh instance. Pass material for a different treatment, or use createDefaultContainerMaterial to adjust the standard recipe:import { useMemo, type ReactNode } from 'react';
import { Container, createDefaultContainerMaterial } from '@wearables-ui-toolkit/mrbd';
export function ShadowedTile({ children }: { children: ReactNode }) {
const material = useMemo(
() => createDefaultContainerMaterial({ withDropShadow: true }),
[],
);
return <Container material={material}>{children}</Container>;
}
DefaultContainerMaterialOptions accepts withDropShadow, hideGlowForStates (the visual states that suppress the glow stroke), and a partial config override. Keep the material in useMemo so each mounted component instance owns one. Creating it on every render discards layer caches, and mounting one material instance in two containers at the same time throws. Shape providers are immutable and can be shared.ShapeProvider supplies the fill and stroke paths used by the clip and every material layer, keeping the glow, border, and clip aligned:import { CornerRadius, RoundedRectangleShapeProvider } from '@wearables-ui-toolkit/mrbd';
const tileShape = new RoundedRectangleShapeProvider(CornerRadius.MEDIUM);
RoundedRectangleShapeProvider accepts a CornerRadius case and an optional corner-smoothing factor. TailShapeProvider accepts a ShapeTailDirection, followed by the same two arguments, and creates a tooltip-style shape with a tail. Without shapeProvider, the component uses a rounded rectangle with CornerRadius.LARGE. Use getShapeProviderId when application caching needs a stable provider identity.| Export | Members | Meaning |
|---|---|---|
State | DEFAULT, FOCUSED, PRESSED | The user’s current interaction with the control. |
VisualState | NONE, DEFAULT, FOCUSED, PRESSED | The material’s rendered state. NONE omits the resting backdrop. |
InteractionState | { state: State; isDisabled: boolean } | The combined value reported by onStateChange. |
ContentScaleInsets | per-platform, per-state horizontal insets | The source of content scaling between states. |
Platform | 'mrbd' | The target used for content-scale calculations. |
onStateChange only when component content needs to react, such as swapping an icon or changing text emphasis. Let the base own the remaining interaction:import { State } from '@wearables-ui-toolkit/mrbd';
import type { InteractionState } from '@wearables-ui-toolkit/mrbd';
function handleStateChange(_previous: InteractionState, next: InteractionState) {
setShowsDetail(next.state === State.FOCUSED);
}
disabled keeps the element focusable, preventing holes in directional navigation, and the toolkit applies aria-disabled and disabled opacity.onActivate. It fires exactly once per activation and includes the originating keyboard or mouse event. onClick also fires for each real DOM click, including the click dispatched by keyboard activation, so using both handlers for the action invokes it twice.partialFocusSupportedAxis: PartialFocusSupportedAxis.XY is the default, X and Y restrict it to one axis, and None opts out. Set isRubberbandTranslationEnabled to false when the element itself cannot move. onPartialFocusHandoff and onInvalidFocusDirection let component visuals acknowledge these events; focus movement remains owned by the toolkit.Container’s visualStateOverride and materialTransition for driven previews, such as a gallery showing every state. They do not replace real interaction state in an application component.visualStateForInteractionStateFn or contentScaleForStateFn only for a component with a genuinely different state mapping, such as a control that omits its resting backdrop until focused.Omit<ContainerProps, 'children'> for an interactive component built on Container, or UITCommonProps for a standalone component.size prop with three cases defines a stable API, while a styleOverrides object exposes implementation details.className and style so the application can position the component without replacing its own styles.ref as a normal prop and forward it to the element callers expect to measure or focus.import {
Container,
CornerRadius,
RoundedRectangleShapeProvider,
TextColor,
TextStyle,
TextView,
} from '@wearables-ui-toolkit/mrbd';
import type { ContainerProps } from '@wearables-ui-toolkit/mrbd';
import type { Ref } from 'react';
const statTileShape = new RoundedRectangleShapeProvider(CornerRadius.MEDIUM);
export interface StatTileProps extends Omit<ContainerProps, 'children'> {
/** Short caption describing the measurement. */
label: string;
/** Ref to the tile's root element. */
ref?: Ref<HTMLDivElement>;
/** Formatted value to display. */
value: string;
}
export function StatTile({
className = '',
label,
ref,
value,
...containerProps
}: StatTileProps) {
return (
<Container
{...containerProps}
ref={ref}
ariaLabel={`${label}: ${value}`}
className={`stat-tile ${className}`}
shapeProvider={statTileShape}
>
<TextView textStyle={TextStyle.NUMERAL2}>{value}</TextView>
<TextView textStyle={TextStyle.META1} textColor={TextColor.SECONDARY}>
{label}
</TextView>
</Container>
);
}
onActivate, disabled, tooltipText, data-uit-*, or any div attribute without additional support from the component.InteractableBase and its derived components provide role="button" for clickable div roots, manage the tab index (including removing it when the element is not focusable), derive aria-disabled from disabled, map either the standard prop or ariaLabel alias to aria-label, and pass through aria-valuenow, aria-valuemin, aria-valuemax, and aria-checked.ariaLabel from the component’s semantic props when possible, as in the example above.role for a control that is not a button, such as a switch, radio, slider, tab, or menu item.tooltipContent instead of tooltipText.Tab and arrow keys. Native tab traversal is preserved across the toolkit by default, and a component must not interrupt it because keyboard and assistive-technology users depend on that path.| Export | Use |
|---|---|
AnimationDurations | Named durations, including CONTAINER_STATE_CHANGE, CONTAINER_PRESS_IN, and CONTAINER_PRESS_OUT. |
Interpolators | Named cubic-bezier easing strings for those durations. |
SpringConfigs | Named spring presets, including SOFT, STANDARD, BOUNCY, and SNAPPY. |
createTransition | Builds a CSS transition string from properties, a duration, an interpolator, and an optional delay. |
useSpringAnimation | A frame-rate-limited spring controller for imperative animation. |
usePrefersReducedMotion | Reads the user’s reduced-motion setting. |
reducedMotionDuration | Collapses a duration to zero when reduced motion is preferred. |
import {
AnimationDurations,
Interpolators,
createTransition,
reducedMotionDuration,
usePrefersReducedMotion,
} from '@wearables-ui-toolkit/mrbd';
function useDetailTransition() {
const prefersReducedMotion = usePrefersReducedMotion();
return createTransition(
'opacity',
reducedMotionDuration(
AnimationDurations.TEXT_OPACITY,
prefersReducedMotion,
),
Interpolators.TEXT_OPACITY,
);
}
@media (prefers-reduced-motion: reduce) rule that collapses the animation. useSpringAnimation already snaps to its target when reduced motion is preferred. Its onChange callback runs on the animation hot path, capped at 30 updates per second by default and configurable through frameRate. Write to the DOM or another imperative sink in that callback rather than scheduling React state updates on every frame.