Files
xrpl-dev-portal/shared/sections/LogoSquareGrid
Calvin 5021556c8e BDS component polish: link contrast, static cards, uniform CTAs, stacked hero (#3851)
* 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>
2026-08-18 16:49:10 -07:00
..

LogoSquareGrid Component

A responsive grid pattern for displaying company/partner logos with an optional header section. Built on top of the TileLogo component, featuring square tiles arranged in a responsive grid with 2 color variants and full dark mode support.

Features

  • 2 Color Variants: Gray and Green backgrounds
  • Responsive Grid: Automatically adapts from 2 columns (mobile) to 4 columns (tablet/desktop)
  • Optional Header: Includes heading, description, and action buttons
  • Clickable Logos: Support for optional links on individual logos
  • Dark Mode Support: Full light and dark mode compatibility
  • Square Tiles: Maintains perfect square aspect ratio at all breakpoints
  • Grid Integration: Built-in PageGrid wrapper with standard container support

Responsive Behavior

The component automatically adapts its grid layout based on viewport width:

Breakpoint Columns Gap Tile Size
Mobile (< 768px) 2 8px ~183px
Tablet (768px - 1023px) 4 8px ~178px
Desktop (≥ 1024px) 4 8px ~298px

Color Variants

The LogoSquareGrid pattern uses two color variants that map directly to TileLogo component variants:

LogoSquareGrid Variant TileLogo Variant Description
gray neutral Subtle, professional appearance for general partner showcases
green green Highlights featured or primary partners

Colors are managed by the TileLogo component and automatically adapt between light and dark modes with proper hover states and animations.

Props API

interface LogoItem {
  /** Logo image source URL */
  logo: string;
  /** Alt text for the logo image */
  alt: string;
  /** Optional link URL - makes the logo clickable */
  href?: string;
  /** Optional click handler - makes the logo a button */
  onClick?: () => void;
  /** Disabled state */
  disabled?: boolean;
}

interface LogoSquareGridProps {
  /** Color variant - determines background color */
  variant?: 'gray' | 'green';
  /** Optional heading text */
  heading?: string;
  /** Optional description text */
  description?: string;
  /** Button configurations (1-2 buttons supported) */
  buttons?: ButtonConfig[];
  /** Array of logo items to display in the grid */
  logos: LogoItem[];
  /** Additional CSS classes */
  className?: string;
}

Note: LogoItem extends TileLogoProps from the TileLogo component. The variant property is NOT included in individual logo items - it's controlled at the component level via the LogoSquareGridProps.variant prop, which applies to all tiles in the grid.

Default Values

  • variant: 'gray'
  • heading: undefined
  • description: undefined
  • buttons: undefined
  • className: ''

ButtonConfig

Imported from the ButtonGroup pattern.

interface ButtonConfig {
  label: string;        // Button text
  href?: string;        // Link destination
  onClick?: () => void; // Click handler
  forceColor?: boolean; // Keep the color constant regardless of theme mode
}

A maximum of 2 buttons is supported. ButtonGroup assigns variants by count: a single button renders as primary; with two, the first is primary and the second tertiary.

Required Props

  • logos: Array of logo items (required)

Usage Examples

Basic Usage with Gray Variant

import { LogoSquareGrid } from 'shared/sections/LogoSquareGrid';

<LogoSquareGrid
  variant="gray"
  logos={[
    { logo: "/img/logos/company1.svg", alt: "Company 1" },
    { logo: "/img/logos/company2.svg", alt: "Company 2" },
    { logo: "/img/logos/company3.svg", alt: "Company 3" },
    { logo: "/img/logos/company4.svg", alt: "Company 4" }
  ]}
/>

With Header Section

<LogoSquareGrid
  variant="green"
  heading="Developer tools & APIs"
  description="Streamline development and build powerful RWA tokenization solutions with XRP Ledger's comprehensive developer toolset."
  buttons={[
    { label: "View Documentation", href: "/docs" },
    { label: "Explore Tools", href: "/tools" }
  ]}
  logos={[
    { logo: "/img/logos/tool1.svg", alt: "Tool 1" },
    { logo: "/img/logos/tool2.svg", alt: "Tool 2" },
    { logo: "/img/logos/tool3.svg", alt: "Tool 3" },
    { logo: "/img/logos/tool4.svg", alt: "Tool 4" },
    { logo: "/img/logos/tool5.svg", alt: "Tool 5" },
    { logo: "/img/logos/tool6.svg", alt: "Tool 6" },
    { logo: "/img/logos/tool7.svg", alt: "Tool 7" },
    { logo: "/img/logos/tool8.svg", alt: "Tool 8" }
  ]}
/>

With Clickable Logos

<LogoSquareGrid
  variant="gray"
  heading="Our Partners"
  description="Leading companies building on XRPL."
  logos={[
    {
      logo: "/img/logos/partner1.svg",
      alt: "Partner 1",
      href: "https://partner1.com"
    },
    {
      logo: "/img/logos/partner2.svg",
      alt: "Partner 2",
      href: "https://partner2.com"
    }
  ]}
/>

With Button Handlers

<LogoSquareGrid
  variant="green"
  heading="Interactive Partners"
  description="Click any logo to learn more."
  logos={[
    {
      logo: "/img/logos/partner1.svg",
      alt: "Partner 1",
      onClick: () => openModal('partner1')
    },
    {
      logo: "/img/logos/partner2.svg",
      alt: "Partner 2",
      onClick: () => openModal('partner2')
    }
  ]}
/>

With Disabled State

<LogoSquareGrid
  variant="gray"
  heading="Coming Soon"
  description="New partners joining the ecosystem."
  logos={[
    {
      logo: "/img/logos/partner1.svg",
      alt: "Partner 1",
      href: "/partners/partner1"
    },
    {
      logo: "/img/logos/coming-soon.svg",
      alt: "Coming Soon",
      disabled: true
    }
  ]}
/>

Without Header (Logo Grid Only)

<LogoSquareGrid
  variant="gray"
  logos={[
    { logo: "/img/logos/sponsor1.svg", alt: "Sponsor 1" },
    { logo: "/img/logos/sponsor2.svg", alt: "Sponsor 2" },
    { logo: "/img/logos/sponsor3.svg", alt: "Sponsor 3" },
    { logo: "/img/logos/sponsor4.svg", alt: "Sponsor 4" }
  ]}
/>

With Single Button

<LogoSquareGrid
  variant="green"
  heading="Featured Integrations"
  description="Connect with leading platforms and services."
  buttons={[{ label: "See All Integrations", href: "/integrations" }]}
  logos={[
    { logo: "/img/logos/integration1.svg", alt: "Integration 1" },
    { logo: "/img/logos/integration2.svg", alt: "Integration 2" }
  ]}
/>

With Click Handler

<LogoSquareGrid
  variant="gray"
  heading="Developer Resources"
  description="Access comprehensive tools and libraries."
  buttons={[
    {
      label: "Get Started",
      onClick: () => console.log('Primary clicked')
    },
    {
      label: "Learn More",
      href: "/learn"
    }
  ]}
  logos={[
    { logo: "/img/logos/resource1.svg", alt: "Resource 1" }
  ]}
/>

Important Implementation Details

Logo Image Requirements

For best results, logo images should:

  • Be SVG format for crisp scaling
  • Have transparent backgrounds
  • Be reasonably sized (width: 120-200px recommended)
  • Use monochrome or simple color schemes
  • Have consistent visual weight across all logos

Grid Behavior

  • The grid uses PageGridCol components for responsive layout
  • Each tile uses span={{ base: 2, lg: 3 }} (2 cols on mobile out of 4, 3 cols on desktop out of 12)
  • Tiles maintain a 1:1 aspect ratio using aspect-ratio: 1
  • Gaps between tiles are handled by PageGrid's built-in gutter system
  • Grid automatically wraps to new rows as needed

Clickable Logo Behavior

Logo tiles leverage the TileLogo component's interactive capabilities:

  • With href property: Renders as a link (<a> tag) with window shade hover animation
  • With onClick property: Renders as a button with the same interactive states
  • With disabled property: Prevents interaction and applies disabled styling
  • Interactive states: Default, Hover, Focused, Pressed, and Disabled
  • Animation: Window shade effect that wipes from bottom to top on hover
  • All tiles automatically maintain focus states for keyboard accessibility

Header Section Logic

The header section only renders if at least one of the following is provided:

  • heading
  • description
  • buttons

Button Styling

  • Buttons use the green color scheme
  • Buttons stack vertically on mobile, horizontal on tablet+
  • Button spacing: 8px gap on mobile, 4px gap on tablet+
  • Button layout is handled by the shared ButtonGroup component

Styling

BEM Class Structure

.bds-logo-square-grid                  // Base component
.bds-logo-square-grid--gray            // Gray variant (maps to TileLogo 'neutral')
.bds-logo-square-grid--green           // Green variant (maps to TileLogo 'green')
.bds-logo-square-grid__header          // Header section container
.bds-logo-square-grid__text            // Text content container
.bds-logo-square-grid__heading         // Heading element
.bds-logo-square-grid__description     // Description element

Note: Individual logo tiles are rendered using the TileLogo component with its own BEM structure (bds-tile-logo). Grid layout is handled by PageGridRow and PageGridCol components. Button layout is handled by the ButtonGroup component (bds-button-group).

Typography Tokens

  • Heading: Uses heading-md type token (Tobias Light font)

    • Desktop: 40px / 46px line-height / -1px letter-spacing
    • Tablet: 36px / 45px line-height / -0.5px letter-spacing
    • Mobile: 32px / 40px line-height / 0px letter-spacing
  • Description: Uses body-l type token (Booton Light font)

    • Desktop: 18px / 26.1px line-height / -0.5px letter-spacing
    • Tablet: 18px / 26.1px line-height / -0.5px letter-spacing
    • Mobile: 18px / 26.1px line-height / -0.5px letter-spacing

Color Tokens

All colors are sourced from styles/_colors.scss:

// Tile backgrounds
$gray-200     // Gray variant (light mode)
$gray-700     // Gray variant (dark mode)
$green-200    // Green variant (light mode)
$green-300    // Green variant (dark mode)

Accessibility

  • Semantic HTML structure with proper heading hierarchy
  • All logos include descriptive alt text
  • Clickable logos have proper link semantics
  • Keyboard navigation support with visible focus states
  • ARIA labels provided through Button component
  • Color contrast meets WCAG AA standards in all variants

Best Practices

When to Use Each Variant

  • Gray: General-purpose logo grids, subtle integration
  • Green: Featured partnerships, brand-focused sections

Content Guidelines

  • Heading: Keep concise (1 line preferred), use sentence case
  • Description: Provide context (2-3 lines max), complete sentences
  • Logo Count: Aim for multiples of 4 for visual balance on desktop
  • Alt Text: Use company/product names, not generic "logo"

Logo Preparation

  1. Consistent Sizing: Ensure all logos have similar visual weight
  2. Format: Use SVG for scalability and crisp rendering
  3. Background: Transparent backgrounds work best
  4. Color: Consider providing light/dark variants if needed
  5. Padding: Include minimal internal padding in the SVG itself

Performance

  • Use optimized SVG files (run through SVGO or similar)
  • Consider lazy loading for grids with many logos
  • Provide appropriate alt text for all images
  • Use width and height attributes on img tags when possible

Technical Implementation

  • Grid System: Uses PageGridCol with span={{ base: 2, lg: 3 }} for responsive layout (2 cols mobile, 4 cols desktop)
  • Tile Rendering: Leverages TileLogo component for all logo tiles
  • Variant Mapping: LogoSquareGrid 'gray' → TileLogo 'neutral', LogoSquareGrid 'green' → TileLogo 'green'
  • Interactive States: TileLogo handles href (links), onClick (buttons), and disabled states
  • Aspect Ratio: Square tiles maintained by TileLogo with CSS aspect-ratio: 1
  • Animations: Window shade hover effect managed by TileLogo component
  • Button Layout: Uses shared ButtonGroup component with gap="small" (4px on tablet+)

Files

  • LogoSquareGrid.tsx - Component implementation
  • LogoSquareGrid.scss - Styles with color variants and responsive breakpoints
  • index.ts - Barrel exports
  • README.md - This documentation
  • TileLogo: Core component used to render individual logo tiles with interactive states
  • ButtonGroup: Shared pattern used for responsive button layout in the header
  • PageGrid: Used internally for responsive grid structure and standard container support

Design References

  • Figma Design: Pattern Logo - Square Grid
  • Showcase Page: /about/logo-square-grid-showcase.page.tsx
  • Component Location: shared/sections/LogoSquareGrid/

Version History

  • January 2026: Initial implementation
    • Figma design alignment with 2 color variants
    • Responsive grid with 2/4 column layout
    • Optional header section with buttons
    • Clickable logo support
    • Refactored to use shared ButtonGroup component for button layout