The CardImage component is a responsive card implementation following the XRPL Brand Design System (BDS). It displays an image, title, subtitle, and call-to-action button with three responsive size variants that adapt to viewport width.
- Three Responsive Variants: LG (≥992px), MD (576px-991px), SM (<576px)
- Interactive States: Default, Hover, Focus, Pressed, Disabled
- Button Animation on Card Hover: Hovering the card triggers the button's hover animation
- Flexible Usage: Supports both link navigation and click handlers
- Accessibility: Keyboard navigation and screen reader support
interface CardImageProps {
/** Image source URL */
image: string;
/** Alt text for the image */
imageAlt: string;
/** Card title (1 line only) */
title: string;
/** Card subtitle (max 3 lines). `\n` renders as a line break; an all-bullet subtitle renders as a list. */
subtitle: string;
/** Button label text */
buttonLabel: string;
/** Link destination (renders card as clickable link) */
href?: string;
/** Click handler for the button */
onClick?: () => void;
/** Disabled state */
disabled?: boolean;
/** Optional className for custom styling */
className?: string;
/** When true, image fills entire container with object-fit: cover (no visible background) */
fullBleed?: boolean;
/** Custom background color for image container (defaults to gray-100) */
backgroundColor?: string;
}disabled:falseclassName:''fullBleed:falsebackgroundColor:undefined(falls back to the gray-100 image background)
subtitle is a plain string, but it is not rendered blindly:
- Lines are split on
\n, and blank lines are dropped. - If every line begins with a bullet marker (
•,-, or*), the subtitle renders as a<ul>(bds-card-image__subtitle--list) with one<li>per line. - Anything else renders as a single
<p>, wherewhite-space: pre-linemeans\nstill produces a visible line break.
// Renders as a bulleted list
<CardImage subtitle={"• Fast settlement\n• Low fees\n• Built-in DEX"} ... />
// Renders as a paragraph with a line break
<CardImage subtitle={"Fast settlement.\nLow fees."} ... />By default the image sits in a fixed-height container against a gray-100 background, preserving its aspect ratio.
fullBleed— setsbds-card-image--full-bleed, which drops the card's fixed height (height: auto) and makes the image fill the container using a 1:1 aspect ratio withobject-fit: cover. Use this when the image is the whole visual and you don't want letterboxing.backgroundColor— sets the--bds-card-image-bgcustom property on the image container, overriding the gray-100 default. Has no visible effect alongsidefullBleed, since the image covers the container.
<CardImage fullBleed image="/images/hero.png" ... />
<CardImage backgroundColor="#F3F1EB" image="/images/logo.svg" ... />- Card height: 620px
- Image height: 400px (1:1 aspect ratio preserved)
- 3-column grid width
- Card height: 560px
- Image height: 280px
- 2-column grid width
- Card height: 536px
- Image height: 268px
- 1-column grid width (full width)
The default interactive state with the button showing its primary green background.
When the user hovers over the card:
- Button background fills from bottom-to-top (green-300 → green-200)
- Arrow icon line shrinks
- Gap between label and icon increases
When the card receives keyboard focus:
- Black border outline around the card
- Button shows focus styling
When the button is being pressed:
- Button returns to default styling momentarily
When disabled={true}:
- Card is non-interactive
- Button shows disabled styling (gray background, no icon)
- Text colors muted
- Cursor changes to
not-allowed
import { CardImage } from 'shared/components/CardImage';
<CardImage
image="/images/docs-hero.png"
imageAlt="Documentation illustration"
title="Documentation"
subtitle="Access everything you need to get started working with the XRPL."
buttonLabel="Get Started"
href="/docs"
/><CardImage
image="/images/feature.png"
imageAlt="Feature illustration"
title="New Feature"
subtitle="Learn about our latest feature and how it can help you build better applications."
buttonLabel="Learn More"
onClick={() => console.log('clicked')}
/><CardImage
image="/images/coming-soon.png"
imageAlt="Coming soon"
title="Coming Soon"
subtitle="This feature is not yet available."
buttonLabel="Unavailable"
disabled
/><CardImage
image="/images/hero.png"
imageAlt="Hero image"
title="Custom Styled"
subtitle="Card with additional custom styling."
buttonLabel="Explore"
href="/explore"
className="my-custom-card"
/>| Token | Value | Description |
|---|---|---|
| Image-to-content gap | 24px | Gap between image and content area |
| Title-to-subtitle gap | 12px | Gap between title and subtitle |
| Content horizontal padding | 8px | Left/right padding for content |
| Button margin-top | 30px | Button locked to bottom |
| Border radius | 16px | Card corner radius |
| Element | Light Mode | Dark Mode |
|---|---|---|
| Card background | #FFFFFF | Gray 800 |
| Card border | Gray 300 (#CAD4DF) | Gray 700 |
| Image background | Gray 100 (#F0F3F7) | Gray 700 |
| Text color | #141414 | White |
| Element | Token | Specs |
|---|---|---|
| Title | .sh-md-l | 28px/35px, -0.5px letter-spacing, light weight |
| Subtitle | .body-l | 18px/26.1px, -0.5px letter-spacing, light weight |
The card tracks hover state via React's useState. When hovered:
- A
bds-card-image--hoveredclass is added to the card - CSS rules target the nested Button component and apply hover styles
- The button's
::beforepseudo-element animates (background fill) - The arrow icon line shrinks via
scaleX(0)
This approach ensures the button animation triggers even when hovering areas of the card outside the button itself.
When href is provided:
- The entire card becomes clickable
- Clicking anywhere on the card navigates to the href
- The card is keyboard accessible (Enter/Space to activate)
The component uses the existing BDS Button component with:
variant="primary"- Solid green backgroundcolor="green"- Green color theme
- Tab: Focus the card
- Enter/Space: Activate the card (navigate to href or trigger onClick)
- Card has appropriate
role="link"when href is provided - Image has descriptive alt text
aria-disabledattribute for disabled state
shared/components/CardImage/
├── CardImage.tsx # Component implementation
├── CardImage.scss # Component styles
├── CardImage.md # This documentation
└── index.ts # Exports- Button: Used internally for the CTA button
- CardOffgrid: Similar card component with different layout
The component uses modern CSS features:
- CSS Grid/Flexbox
- CSS transforms and transitions
:focus-visiblepseudo-class-webkit-line-clampfor text truncation
All features are widely supported in modern browsers.