* BDS component polish: link visibility, static cards, uniform CTAs Component-level fixes and refinements across the 2026 brand components. Accessibility / contrast - StandardCard: pin description link colors. Every card variant has a light background in both themes, so theme-level link colors were wrong inside it — dark mode painted plain anchors white and BdsLink lilac-300, both of which wash out on a pale card. Links (including :visited) now use the card's own text color with an underline carrying the affordance; hover uses lilac-500, which clears 4.5:1 on all four card backgrounds. - Breadcrumbs: point the open dropdown menu at the breadcrumb color tokens so the trail, trigger, and menu read as one color in both themes, and give dark mode gray-6 (7.41:1, matching light mode's 7.23:1). Interaction - CardOffgrid: drop every hover affordance when a card has neither href nor onClick. Static cards no longer get a pointer cursor, the color-wipe overlay, or a pressed state — the tokenization and trading carousels pass link-less cards and were advertising a click target that did not exist. - CarouselFeatured: add a 'fade' transition alongside the default 'slide', for decks whose slides share a heading. Inactive slides are now inert, keeping focus and pointer events out of them in both styles. - Add a bds-reduced-motion mixin so components can collapse motion to an instant state change. Design consistency - ButtonGroup: add forceVariant and forceNoPadding overrides, letting a section opt out of the count-based variant defaults and render one uniform treatment. Existing consumers are unaffected. - FeatureTwoColumn: render every link as a tertiary button regardless of count, flush with the title and description. - CardImage: render an all-bullet subtitle as a real <ul> so wrapped text hangs under the first character and screen readers announce it as a list. - docs: give the node-installation card descriptions a paragraph break before their "Learn More" link. Dependencies - Bump @codemirror/state and view, add lang-javascript, lang-json, and lint, with overrides pinning state/view to one copy. Docs updated alongside each component. Built CSS regenerated with the production script to match the committed artifact's minified format. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ButtonGroup: don't strip padding from forced filled variants `noPadding` was derived as `forceNoPadding || isMultiButton`, which was safe while the 3+ block layout was always tertiary. `forceVariant` also accepts `primary` and `secondary`, so a 3-button group forcing a filled variant had `padding: 0 !important` (Button.scss) applied with no way to opt out. Tie the implied no-padding to the resolved variant being tertiary. Behavior is unchanged for every caller that doesn't pass `forceVariant`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stacked hero variant, fade-by-default carousel, component docs Layout - HeaderHeroPrimaryMedia: add a `stacked` prop that puts the headline, subtitle, and buttons in one column (full width at base, 7/8 at md, 9/12 at lg) instead of the default headline-left / CTA-right split. The headline and CTA block bottom-align against each other in the two-column layout, so the stacked variant undoes that. Both arrangements now share the same headline and CTA elements, so they can't drift apart. Used on the docs landing hero. Motion - CarouselFeatured: make `fade` the default transition. Slides that share a heading are the common case, and a horizontal wipe drags the identical heading across the screen only to set it back down. `slide` is now opt-in for decks whose panels are genuinely distinct. Nudge the crossfade to 260ms in / 200ms out. The home page carousel opts into fade explicitly; developer-funding now inherits it. Fixes - CardTextIconCard: pass `headingAs` through to `cardContent`, which was accepting the prop but never receiving it. Docs - Expand the Button, CardImage, CardTextIcon, PageGrid, CalloutMediaBanner, LogoSquareGrid, and HeaderHeroPrimaryMedia references. Built CSS regenerated with the production script. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.8 KiB
CardImage Component Documentation
Overview
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.
Features
- 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
Props API
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;
}
Default Values
disabled:falseclassName:''fullBleed:falsebackgroundColor:undefined(falls back to the gray-100 image background)
Subtitle Formatting
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."} ... />
Image Sizing
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" ... />
Responsive Variants
LG (Large) - Desktop (≥992px)
- Card height: 620px
- Image height: 400px (1:1 aspect ratio preserved)
- 3-column grid width
MD (Medium) - Tablet (576px - 991px)
- Card height: 560px
- Image height: 280px
- 2-column grid width
SM (Small) - Mobile (<576px)
- Card height: 536px
- Image height: 268px
- 1-column grid width (full width)
States
Default State
The default interactive state with the button showing its primary green background.
Hover State
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
Focus State
When the card receives keyboard focus:
- Black border outline around the card
- Button shows focus styling
Pressed State
When the button is being pressed:
- Button returns to default styling momentarily
Disabled State
When disabled={true}:
- Card is non-interactive
- Button shows disabled styling (gray background, no icon)
- Text colors muted
- Cursor changes to
not-allowed
Usage Examples
Basic Card with Link
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"
/>
Card with Click Handler
<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')}
/>
Disabled Card
<CardImage
image="/images/coming-soon.png"
imageAlt="Coming soon"
title="Coming Soon"
subtitle="This feature is not yet available."
buttonLabel="Unavailable"
disabled
/>
Card with Custom Class
<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"
/>
Design Tokens
Spacing
| 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 |
Colors
| 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 |
Typography
| 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 |
How It Works
Hover Animation
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.
Link Navigation
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)
Button Integration
The component uses the existing BDS Button component with:
variant="primary"- Solid green backgroundcolor="green"- Green color theme
Accessibility
Keyboard Navigation
- Tab: Focus the card
- Enter/Space: Activate the card (navigate to href or trigger onClick)
Screen Reader Support
- Card has appropriate
role="link"when href is provided - Image has descriptive alt text
aria-disabledattribute for disabled state
File Structure
shared/components/CardImage/
├── CardImage.tsx # Component implementation
├── CardImage.scss # Component styles
├── CardImage.md # This documentation
└── index.ts # Exports
Related Components
- Button: Used internally for the CTA button
- CardOffgrid: Similar card component with different layout
Browser Support
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.