Customization & composition

Updated: Sep 24, 2026
Compose the toolkit components through their public content, callback, styling, and ref APIs. The toolkit keeps ownership of shape, material, focus, motion, typography, and spacing so interfaces remain consistent and accessible. This page defines the supported customization boundary.

Standard DOM attributes

Most public components forward unrecognized props such as id, data-*, aria-*, title, and event handlers to their root element. Check the component group before relying on this behavior:
GroupWhat you getExamples
Interactable
The InteractableBaseProps surface: standard DOM attributes for the root element, data-uit-* keys, the as prop, and the toolkit interaction props (disabled, interactive, focusable, initialFocusEligible, pressable, clickable, onActivate, onLongPress, onStateChange, ariaLabel, the tooltip* props, partialFocusSupportedAxis, isRubberbandTranslationEnabled, onInvalidFocusDirection, onPartialFocusHandoff). A component drops the ones that do not apply to it — Modal, for example, omits clickable, focusable, pressable, and onClick
InteractableBase, Container, Surface, Panel, Card, CardStack, Modal, Button, QuickReplyButton, ListItem, ControlTile, AppControlTile, IsolatedControl, SubNavigation, ContextMenuItemView, VerticalMenuButton
Static DOM
Standard DOM attributes for the root element, via HTMLAttributes or the exported UITCommonProps
Page, Avatar, StaticContainer, Chip, Tag, ActionHint, ContainerHeader, Divider, Scrim, Vignette, MediaWrapper, IconWithContainerMaterial, Shimmer, ShimmerItem, TextView, TextSwitcher, ReadMoreTextView, AppBadge, NotificationBadge, PaginationIndicator, ProgressIndicator, ProgressRing, CircularProgressBar, IndeterminateLoader, VolumeIndicator, ZoomIndicator, Scrubber, SwipeToReveal, Pager, PageTransition, RoutePreloadTarget, Switch, RadioButton, SliderBar, VerticalMenu
Closed props
Only the declared props. Extra DOM attributes are not forwarded, so use the component’s own className, style, and ariaLabel props
App, ScrollView, VerticalList, Carousel, ButtonRail, ButtonGroup, ButtonDivider, Header, SubNavigationPager, ToastContainer, Tooltip, TooltipContainer, IconImage, PagerPage, CardAboveScrim, CardBelowScrim. SwipeIndicator is nearly closed: it accepts data-*, id, and a fixed set of ARIA attributes. ContextMenu types the full static-surface prop set but only applies className, style, role, aria-label, and aria-disabled
import { Button } from '@wearables-ui-toolkit/mrbd';

<Button title="Reply" id="reply-btn" data-testid="reply" aria-label="Reply" />;
The component preserves its own class composition. An interactable also drops a caller-provided tabIndex unless it is focusable. The toolkit computes role and managed ARIA state from component props; an explicit role, tabIndex, or aria-* replaces that result and can break the component’s semantics, so do not override managed values. Interactables also accept ariaLabel as a convenience alias, while a standard aria-label takes precedence when both are present.

className merging

Pass className for supported application styling. The toolkit appends it to the component’s own classes instead of replacing them, preserving the component’s base styling.
import { Container } from '@wearables-ui-toolkit/mrbd';

<Container className="my-layout-utility">{children}</Container>;
When the root is not the appropriate styling target, a component may expose a second class hook for a specific inner element:
ComponentExtra class propApplies to
ScrollView
scrollContainerClassName
The scrolling viewport
VerticalList
contentClassName
The row container
ListItem
contentClassName
The row’s internal content layout
StaticContainer
contentClassName
The content wrapper inside the material
ReadMoreTextView
textClassName
The clamped text element
PageTransition
pageClassName
Each animating page wrapper
Components built on StaticContainer — Chip, Tag, ActionHint, and VerticalMenu — inherit its contentClassName for the same purpose.

style merging

Use style for layout concerns such as margin, flex behavior, and grid placement. The toolkit shallow-merges it into the computed style, with caller properties applied last where the component does not need to retain ownership. A small set of correctness-sensitive properties remains component-owned and is applied after the caller style. For example, an interactable with a tooltip forces its root position to relative; wrap it when the application needs separate positioning.
Use the theme for colors, radii, typography, and materials rather than repainting the toolkit-owned visuals. See Theming and customization.
VerticalList exposes contentStyle for the row container and ReadMoreTextView exposes textStyleOverride for the clamped text element, for the same reason as their extra class props.

Refs

Components that accept a ref support object and callback refs. Callback refs use React 19 cleanup semantics: the callback can return a cleanup function, which runs when the node detaches.
Choose the ref shape exposed by the component.
DOM element refs support measurement and anchoring:
import { useRef } from 'react';
import { Container } from '@wearables-ui-toolkit/mrbd';

function Anchored() {
  const ref = useRef<HTMLDivElement>(null);
  return <Container ref={ref}>{/* ... */}</Container>;
}
Components in this group include InteractableBase, Container, StaticContainer, Panel, Surface, Card, CardAboveScrim, CardBelowScrim, ListItem, QuickReplyButton, ControlTile, AppControlTile, ContextMenuItemView, VerticalMenuButton, Chip, Tag, Avatar, Modal, Shimmer, ShimmerItem, TextView, and IconWithContainerMaterial. ScrollView and VerticalList forward their ref to the scrolling viewport element rather than the outer frame.
Imperative handles expose behavior that has no declarative equivalent. A handle is not the DOM node:
ComponentHandleNotable members
Button
ButtonHandle
animateActionTransition(options), getElement()
Page
PageHandle
getHeaderHeight(), getElement()
Header
HeaderHandle
getHeaderHeight(), getElement()
ButtonRail
ButtonRailHandle
updateScrollPosition(animated), getElement()
Carousel
CarouselHandle
scrollToPosition(index), getRootElement()
ContextMenu
ContextMenuHandle
scrollToItem(index, smooth?), getElement()
SubNavigation
SubNavigationHandle
setVisible(visible, animated?), getElement()
IsolatedControl
IsolatedControlHandle
setValue(value, animated?), getElement()
ReadMoreTextView
ReadMoreTextViewHandle
isShowingReadMore(), getElement()
Pager
PagerHandle
pageCount(), isNavigationLocked(), isPageTransitionInProgress(), preloadPageIfNeeded(index), unloadPage(index)
SwipeIndicator
SwipeIndicatorHandle
nudge(count?, animated?)
Most handles expose the root node through getElement() for measurement and anchoring. Carousel names this accessor getRootElement(). ButtonRail uses its root as the horizontally clipping viewport and moves an inner track instead of scrolling that element. PagerHandle is the exception and exposes no DOM node.
import { useRef } from 'react';
import { Button, type ButtonHandle } from '@wearables-ui-toolkit/mrbd';

function SendButton() {
  const ref = useRef<ButtonHandle>(null);
  return (
    <Button
      ref={ref}
      title="Send"
      icon={sendIcon}
      onClick={() => {
        submit();
        ref.current?.animateActionTransition({ endingIcon: sentIcon });
      }}
    />
  );
}

The as prop

Use as on supported polymorphic components to change the rendered element. The accepted props narrow to the selected element’s props.
ComponentDefault elementAccepts
InteractableBase
div
Any element type
Container
div
Any element type
ListItem
div
Any element type
VerticalMenuButton
button
Any element type
TextView
span
span, p, div, h1–h6, label
import { Container, TextView, TextStyle } from '@wearables-ui-toolkit/mrbd';

<Container as="li">
  <TextView as="h2" textStyle={TextStyle.HEADING2}>
    Now playing
  </TextView>
</Container>;
as changes only the element tag; the component continues to own its role, focus semantics, and material. Interactables infer a button role only for a div root. Choose an element whose native HTML semantics match the intended interaction.

Subpath imports

Import any public API from the root entry:
import { Button, Container } from '@wearables-ui-toolkit/mrbd';
For fine-grained imports, components are also available from per-component subpaths:
import { Button } from '@wearables-ui-toolkit/mrbd/Button';
import { Container } from '@wearables-ui-toolkit/foundation/components/Container';
A subpath exports everything declared by its module. This includes CardAboveScrim and CardBelowScrim from @wearables-ui-toolkit/foundation/components/Card, ShimmerItem from .../Shimmer, PagerPage from .../Pager, and ToastContainer from .../Toast.
Two subpaths are not components:
  • @wearables-ui-toolkit/mrbd/styles.css — a self-contained compatibility bundle of Foundation and Meta Ray-Ban Display component styles.
  • @wearables-ui-toolkit/mrbd/react-router — the optional react-router integration, which needs the optional react-router-dom peer dependency.
Not every export has a subpath. Standalone enums and hooks, motion constants, TextAppearance, material APIs, and a few components — including InteractableBase, PageTransition, RoutePreloadTarget, and the context providers (FocusNavigationProvider, FloatingPortalRootProvider, RoutePreloadProvider) — are only importable from the root entry.

Customization boundaries

  • Style through documented props, theme values, and class hooks. Do not target generated class names, query descendant nodes, or write CSS against internal DOM because that structure is not a public API and can change without notice.
  • Supply a ContainerMaterial or ShapeProvider where the component accepts one. Do not replace component-owned material, shape, focus, or press behavior with application CSS. See Materials, motion, focus, and portals.
  • Preserve the role, tabIndex, and managed ARIA state computed by an interactable. These values are required for its accessibility behavior.
  • Use semantic tokens, TextStyle, and exported motion constants. Avoid hardcoded colors, spacing, radii, typography, durations, and viewport dimensions so themes and responsive layouts remain consistent.
  • When no semantic component fits, build on Container, StaticContainer, or InteractableBase to inherit focus, material, and interaction behavior. Avoid imitating a missing component with look-alike markup.
  • Render one App and use the toast container, focus provider, and floating portal root it supplies. Duplicating these owners breaks global coordination.