CardStack when one destination represents a related media collection, such as an album, gallery, or playlist. It presents one primary Card above as many as three background cards.CardStack owns the collection geometry. It normalizes card dimensions to the selected aspect ratio, calculates the bounds for fanned or scattered cards, positions every background card, and scales the group on focus. Background cards preview the collection and remain presentational rather than independent actions.import { AspectRatio, CardStack, ScrimType, StackType } from '@wearables-ui-toolkit/mrbd';
export function AlbumStack({ album, frame, onOpen }: AlbumStackProps) {
return (
<CardStack
ariaLabel={album.title}
aspectRatio={AspectRatio.SQUARE}
backgroundCards={album.previews.slice(0, 2).map(preview => ({
src: preview.url,
alt: '',
}))}
bottomScrim={ScrimType.MEDIUM}
clickable
height={frame.height}
onActivate={onOpen}
type={StackType.INTERACTIVE}
width={frame.width}
>
<img className="album-cover" src={album.coverUrl} alt="" aria-hidden="true" />
</CardStack>
);
}
.album-cover {
width: 100%;
height: 100%;
object-fit: cover;
}
width and height are numeric and size the cards rather than the entire stack box. Derive both from a measured responsive frame when the cards need to fill that frame instead of hardcoding a Meta Ray-Ban Display size. Leave enough space between stacks for their expanded geometry:.album-row {
display: flex;
gap: var(--uit-spacing-large);
}
import { CardStack, StackType } from '@wearables-ui-toolkit/mrbd';
<CardStack
type={StackType.DISPLAY}
interactive={false}
backgroundCards={[
{ children: <PreviewTile tone="mid" /> },
{ children: <PreviewTile tone="low" /> },
]}
maxWidth={rowWidth}
>
<PreviewTile tone="high" />
</CardStack>;
CardStack and its enums are also available from the component subpath:import {
AspectRatio,
CardStack,
StackType,
} from '@wearables-ui-toolkit/foundation/components/CardStack';
CardStackProps. It extends Card props, except for children; those props extend Container and InteractableBase props. The component forwards a ref to the stack root.| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content of the primary card. |
type | StackType | StackType.INTERACTIVE | Stack layout and interaction model. |
aspectRatio | AspectRatio | AspectRatio.PORTRAIT | Aspect ratio applied to every card in the stack. |
backgroundCards | BackgroundCardConfig[] | [] | Preview cards behind the primary card. |
topScrim | ScrimType | ScrimType.NONE | Top gradient scrim on the primary card. |
bottomScrim | ScrimType | ScrimType.NONE | Bottom gradient scrim on the primary card. |
width | number | — | Requested card width in pixels. It bounds the cards, not the whole stack. When omitted, a built-in seed width applies and also caps the cards. |
height | number | — | Requested card height in pixels. When omitted, a built-in seed height applies and also caps the cards. |
maxWidth | number | — | Upper bound in pixels for the measured stack width. |
maxHeight | number | — | Upper bound in pixels for the measured stack height. |
CardStack forwards these props to the primary card unless noted.| Prop | Type | Default | Description |
|---|---|---|---|
shapeProvider | ShapeProvider | rounded rectangle at the medium corner radius | Geometry shared by primary and background cards. It also defines the corner pivot for the interactive fan. |
material | ContainerMaterial | the card material | Primary-card material. Background cards always use the card material. |
interactive | boolean | true | Default value for focusable, pressable, and clickable on the primary card. |
disabled | boolean | false | Blocks activation of the primary card. |
onActivate | (event: InteractableActivationEvent) => void | — | Action for the stack destination. |
onStateChange | (prev: InteractionState, next: InteractionState) => void | — | Called when the primary card’s interaction state changes. The stack drives its scale from the same event. |
ariaLabel | string | — | Accessible name for the stack destination. |
tabIndex | number | 0 | Tab index of the primary card. A DISPLAY stack forces it to -1. |
className | string | '' | Class added to the stack root. |
style | CSSProperties | — | Inline styles applied to the stack root after measured size and focus scale. |
StackTypeINTERACTIVE — the primary card owns interaction, and as many as two background cards fan from a corner pivot when it receives focusDISPLAY — a static scattered arrangement without focus animation, with as many as three background cardsAspectRatioSQUARE — 1:1PORTRAIT — 3:4BackgroundCardConfigsrc?: string — image source for the background cardalt?: string — image alt text, with an empty string as the defaultchildren?: ReactNode — custom content rendered instead of the imageINTERACTIVE stack supports at most two background cards, and a DISPLAY stack supports at most three. Exceeding the applicable limit is a misuse. Development builds throw so the issue is visible immediately; production builds drop extra cards and log a console warning rather than crashing the screen. A stack without background cards is supported and renders as one card.width and height bound each card rather than the entire stack. CardStack finds the largest card with the selected aspect ratio that fits those bounds, calculates the space needed by the fanned or scattered arrangement, and then derives the individual card size from those bounds. If either dimension is omitted, the component uses a seed value. As a result, width alone grows cards only as far as the seed height allows; provide both dimensions when the stack must fill measured space.maxWidth and maxHeight constrain the stack box and shrink the complete arrangement to fit. Rotated and translated background cards make the box wider than one card, and a DISPLAY stack is also taller. The application owns the surrounding layout and should reserve this additional space to prevent clipping.INTERACTIVE stack, the primary card is the only focus target. Its background cards rotate farther from their shared corner pivot on focus, while focus and press scale the whole group on the same motion curve as a container state change. In a DISPLAY stack, the primary card’s tab index is forced to -1 and no focus animation runs. The default interaction props still apply, so use interactive={false} when the stack is presentational. Background cards always have a presentation role and never enter the focus order.CardStack pins their content scale, so a contentScaleForStateFn passed to the stack does not reach the cards. The stack root applies the focus breathing once, which keeps the fanned geometry aligned.children when provided; otherwise, it renders the image from src scaled to cover the card. Without either value, it renders empty. Scrims apply only to the primary card.CardStack applies className, style, and data-uit-* attributes to the stack root and forwards every other inherited prop to the primary card. The component owns the primary card’s style, rendered size, and content scale, so callers cannot set those values through the stack. Use width, height, maxWidth, and maxHeight to shape the arrangement.DISPLAY stack tilts the primary card slightly even without background cards so it still reads as a stack. The stack box does not clip its rotated corners. Leave clearance around the box so nearby content does not collide with them.ariaLabel that describes the collection rather than one image.alt empty, which is also the default, to avoid redundant announcements.DISPLAY stack is not keyboard reachable. Do not add activation semantics or present it as a tap target. Use interactive={false} so its primary card does not expose an unreachable button role.width and height from a measured frame. Use maxWidth or maxHeight when the row must remain within a known bound.INTERACTIVE stack or three for a DISPLAY stack. These are the component’s supported runtime limits.style or custom CSS. CardStack owns their geometry to keep focus animation and responsive sizing consistent.