* adding showcase page * adding CardStatsList * clean up, tighter code * code review and code clean up * update import, clean up env for error message * tweak some css code * less css, rebuilt * re-adding bem, modifier for bds variants
LinkTextDirectory Component
A section pattern that displays a numbered list of LinkTextCard components with a heading and optional description.
Overview
LinkTextDirectory is a section-level pattern that combines a header (heading + description) with a vertically stacked list of LinkTextCard components. Each card is automatically numbered sequentially (01, 02, 03...), making it perfect for feature lists, step-by-step guides, or numbered content sections.
Features
- Automatic Numbering: Cards are numbered sequentially starting from 01
- Responsive Layout: Adaptive spacing and alignment across breakpoints
- Desktop Right-Alignment: Cards are right-aligned on desktop for visual interest
- Minimal HTML Structure: Flat, efficient DOM hierarchy
- Light/Dark Mode: Full theming support
- Flexible Content: Supports any number of cards
Layout Behavior
| Breakpoint | Card Alignment | Gap Between Cards | Header Gap |
|---|---|---|---|
| Base (< 576px) | Left | 24px | 8px |
| MD (576px - 991px) | Left | 32px | 8px |
| LG (≥ 992px) | Right | 40px | 16px |
Desktop Behavior: Cards are right-aligned using align-items: flex-end, creating a visually distinct layout compared to mobile/tablet.
Usage
Basic Usage
<LinkTextDirectory
heading="Explore XRPL Developer Tools"
description="XRP Ledger is a compliance-focused blockchain where financial applications come to life"
cards={[
{
heading: "Fast Settlement and Low Fees",
description: "Settle transactions in 3-5 seconds for a fraction of a cent, ideal for large-scale, high-volume RWA tokenization",
buttons: [
{ label: "Get Started", href: "/start" },
{ label: "Learn More", href: "/docs" }
]
},
{
heading: "Secure and Reliable",
description: "Built on proven blockchain technology with enterprise-grade security",
buttons: [
{ label: "Read Documentation", href: "/docs" }
]
},
{
heading: "Developer Friendly",
description: "Comprehensive APIs and SDKs for seamless integration",
buttons: [
{ label: "View API", href: "/api" },
{ label: "See Examples", href: "/examples" }
]
}
]}
/>
Without Description
<LinkTextDirectory
heading="Key Features"
cards={featuresList}
/>
With Dynamic Data
const features = [
{
heading: "Feature One",
description: "Description for feature one",
buttons: [{ label: "Learn More", href: "/feature-1" }]
},
// ... more features
];
<LinkTextDirectory
heading="Platform Features"
description="Everything you need to build amazing applications"
cards={features}
/>
Props
LinkTextDirectoryProps
| Prop | Type | Default | Description |
|---|---|---|---|
heading |
string |
Required | Section heading |
description |
string |
- | Optional description text |
cards |
LinkTextCardData[] |
Required | Array of card data |
className |
string |
- | Additional CSS classes |
LinkTextCardData
| Prop | Type | Default | Description |
|---|---|---|---|
heading |
string |
Required | Card heading |
description |
string |
Required | Card description |
buttons |
ButtonConfig[] |
Required | Array of button configs (max 2) |
ButtonConfig (from ButtonGroup)
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
Required | Button text |
href |
string |
- | Link destination |
onClick |
() => void |
- | Click handler |
Component Structure
<PageGrid className="bds-link-text-directory">
<PageGridRow>
<PageGridCol className="bds-link-text-directory__header" span={{ base: 12, md: 6, lg: 8 }}>
<h2 className="h-md">{heading}</h2>
<p className="body-l">{description}</p>
</PageGridCol>
</PageGridRow>
<PageGridRow>
<PageGridCol span={{ base: 12, md: 8, lg: 8 }} offset={{ lg: 4 }}>
<ul>
{cards.map((card, index) => (
<LinkTextCard
key={index}
index={index}
heading={card.heading}
description={card.description}
buttons={card.buttons}
/>
))}
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
Key Design Decisions:
- PageGrid Integration: Uses PageGrid system for responsive layout
- Typography Classes: Uses existing
h-mdandbody-lutility classes - Flexbox Header: Header uses flexbox with gap for spacing between heading and description
- Desktop Right-Alignment: Cards offset by 4 columns at LG breakpoint (right-aligned)
- Semantic List: Cards wrapped in
<ul>with each card as<li>
Responsive Spacing
| Breakpoint | Section Padding | Header Gap | Header Margin-Bottom |
|---|---|---|---|
| Base (< 576px) | 24px | 8px | 24px |
| MD (576px - 991px) | 32px | 8px | 32px |
| LG (≥ 992px) | 40px | 16px | 40px |
Section Padding: Top and bottom padding on the entire section Header Gap: Space between heading and description (via flexbox gap) Header Margin-Bottom: Space between header and cards list
Styling
CSS Classes
.bds-link-text-directory- Main PageGrid container with section padding.bds-link-text-directory__header- Header section (flexbox column with gap)
Typography
- Heading:
h-mdclass (responsive heading) - Description:
body-lclass (large body text) - Card content uses LinkTextCard's built-in typography
Grid Layout
- Header Column:
span={{ base: 12, md: 6, lg: 8 }} - Cards Column:
span={{ base: 12, md: 8, lg: 8 }}withoffset={{ lg: 4 }} - Cards are right-aligned on desktop via the 4-column offset
Dark Mode
- Text color changes to white in dark mode
- Applied to entire section via
bds-theme-mode(dark)mixin
Card Numbering
Cards are automatically numbered based on their array index:
cards[0] → LinkTextCard(index: 0) → displays "01"
cards[1] → LinkTextCard(index: 1) → displays "02"
cards[2] → LinkTextCard(index: 2) → displays "03"
// ... and so on
Files
LinkTextDirectory.tsx- React componentLinkTextDirectory.scss- SCSS stylesindex.ts- Barrel exportsREADME.md- This file
Related Components
- LinkTextCard: Used for each card in the list
- ButtonGroup: Used by LinkTextCard for action buttons
Import
import { LinkTextDirectory } from 'shared/sections/LinkTextDirectory';
// or
import {
LinkTextDirectory,
type LinkTextDirectoryProps,
type LinkTextCardData
} from 'shared/sections/LinkTextDirectory';
Design System
Part of the Brand Design System (BDS) with bds- namespace prefix.
Best Practices
- Consistent Card Content: Try to keep similar text lengths across cards for visual balance
- Limit Cards: 3-6 cards works best for readability
- Clear Descriptions: Keep descriptions concise but informative
- Button Labels: Use clear, action-oriented button labels
- Logical Ordering: Order cards by priority or logical sequence
Accessibility
- Semantic HTML with proper heading hierarchy (
<h2>for section,<h5>for cards) - Semantic list structure:
<ul>containing<li>elements - Sequential tab order through cards and buttons
- ARIA-compliant button and link elements (via ButtonGroup)
- Maintains focus order: heading → description → card 1 → card 2 → etc.
Best Practices for React Keys
When mapping over cards, use a stable identifier instead of array index:
// ❌ Avoid using index as key
{cards.map((card, index) => (
<LinkTextCard key={index} ... />
))}
// ✅ Better: Use a unique identifier
{cards.map((card, index) => (
<LinkTextCard key={card.id || card.heading} ... />
))}
// ✅ Best: Add an id field to LinkTextCardData
interface LinkTextCardData {
id: string; // Unique identifier
heading: string;
description: string;
buttons: ButtonConfig[];
}
{cards.map((card) => (
<LinkTextCard key={card.id} ... />
))}
Example with All Features
<LinkTextDirectory
heading="Why Choose XRPL"
description="The most efficient blockchain for real-world applications"
cards={[
{
heading: "Lightning Fast",
description: "Process thousands of transactions per second with sub-3-second finality",
buttons: [
{ label: "View Benchmarks", href: "/performance" },
{ label: "Try Demo", onClick: () => openDemo() }
]
},
{
heading: "Cost Effective",
description: "Minimal transaction fees make XRPL perfect for micro-transactions and high-volume use cases",
buttons: [
{ label: "See Pricing", href: "/pricing" }
]
},
{
heading: "Battle Tested",
description: "Over 10 years of continuous operation with billions of transactions processed",
buttons: [
{ label: "Read Case Studies", href: "/case-studies" },
{ label: "View Stats", href: "/statistics" }
]
}
]}
className="my-custom-class"
/>