Common Examples
Interactive card provides a clickable card interface that supports visually nested interactive elements without accessibility violations. Use the button variant for actions and the link variant for navigation.
Use the button variant when the card triggers an action (e.g., expanding content, opening a modal).import { InteractiveCard } from "@servicetitan/anvil2/beta";
import { Button, Flex, Text } from "@servicetitan/anvil2";
function ExampleComponent() {
return (
<InteractiveCard
wrapperProps={{ "aria-label": "Basic interactive card" }}
actionProps={{
"aria-label": "Select basic interactive card",
onClick: console.log
}}
contentProps={{ padding: "large", flexDirection: "column", gap: "2" }}
>
<Text variant="headline" el="h2" size="medium">
Basic Interactive Card
</Text>
<Text>Basic text content in an interactive card.</Text>
<Flex gap="2">
<Button onClick={console.log}>
Action
</Button>
</Flex>
</InteractiveCard>
);
}
Link Card
Use the link variant when the card navigates to another page or section.import { InteractiveCard } from "@servicetitan/anvil2/beta";
import { Button, Flex, Text } from "@servicetitan/anvil2";
function ExampleComponent() {
return (
<InteractiveCard
wrapperProps={{ "aria-label": "[NAME]'s profile card" }}
actionProps={{
"aria-label": "Navigate to [NAME]'s profile",
href: "/profile/[NAME]",
}}
contentProps={{ padding: "large", flexDirection: "column", gap: "3" }}
>
<Text variant="headline" el="h2" size="medium">
[NAME]
</Text>
<Link href="[NAME]@servicetitan.com">
[NAME]@servicetitan.com
</Link>
<Text>Click card to view project details</Text>
</InteractiveCard>
);
}
Disabled State
Disable user interaction with the disabled prop on actionProps. Only available for button cards (not link cards).The disabled prop only disables the card action layer. Nested interactive elements (buttons, links, etc.) remain fully functional. If you need to disable nested elements to reflect the disabled state, handle that separately in your implementation.
<InteractiveCard
wrapperProps={{ "aria-label": "Disabled project card" }}
actionProps={{
"aria-label": "View project",
onClick: () => {},
disabled: true,
}}
contentProps={{ padding: "large", flexDirection: "column", gap: "3" }}
>
<Text variant="headline" size="medium">
Disabled Project
</Text>
<Text>This card is disabled and cannot be clicked</Text>
<Button onClick={console.log}>
Action
</Button>
</InteractiveCard>
React Accessibility
- Uses a
role="group" wrapper with aria-label (via wrapperProps) to describe the card’s content
- The interactive layer (button or link) has its own
aria-label (via actionProps) describing the action
- Full keyboard support: Tab to navigate between the card and nested elements, Enter/Space to activate
- Touch-friendly: Handles touch events on mobile devices
- WCAG AA 2.2 compliant: No nested button violations due to sibling structure approach
Dual ARIA Labels
Interactive card requires two ARIA labels to maintain accessibility:
wrapperProps["aria-label"]: Describes what the card contains (e.g., “Kitchen Measurement 2 card”)
actionProps["aria-label"]: Describes the action that will be taken (e.g., “Expand Kitchen Measurement 2”)
Screen readers will announce both labels, providing context about the card’s content and the available action.<InteractiveCard
wrapperProps={{ "aria-label": "Project card" }}
actionProps={{
"aria-label": "View project details",
onClick: handleClick,
}}
contentProps={{
padding: "medium",
background: "strong",
}}
>
Card content
</InteractiveCard>
InteractiveCard Props
Interactive card uses a discriminated union type based on the href prop in actionProps: when href is provided, it renders as a link and disabled is not available. When href is not provided, it renders as a button and disabled is available.actionProps
ActionButtonProps | ActionLinkProps
required
Props for the action element (button or link). See ActionButtonProps and ActionLinkProps below for details.
Props for the group wrapper element. See WrapperProps below for details.
Props for the content layer (Card element). Controls padding, background, gap, and other Card styling. See ContentProps below for details.
wrapperProps Object
Props for the group wrapper element.ARIA label for the group wrapper describing the card’s content.
The wrapperProps object extends all standard HTML div attributes via PassThroughProps<"div">, excluding onClick and children.
actionProps Object(s)
Props for the action element when used as a button (when href is not provided).ARIA label for the button action describing what will happen when clicked.
onClick
(event: MouseEvent<HTMLButtonElement>) => void
required
Click handler for the button card.
Disables the button action. Not available for link cards.
The button object extends all standard HTML button attributes via PassThroughProps<"button">, excluding children, type, and href.
Link Cards (InteractiveCardWithLinkProps)
Props for the action element when used as a link (when href is provided).ARIA label for the link action describing where the link will navigate.
URL for the card to navigate to. When provided, the card renders as an anchor element.
onClick
(event: MouseEvent<HTMLAnchorElement>) => void
Optional click handler for the link card. Can be used for analytics tracking alongside navigation.
The link object extends all standard HTML anchor attributes via PassThroughProps<"a">, excluding children, disabled, and type.
contentProps Object
Props for the content layer (Card element). Includes all Card component props.