Add Tertiary Button Showcase Component

Introduced a new ButtonShowcaseTertiary component to demonstrate the BDS Tertiary Button, including its various states and usage examples. Removed the previous ButtonShowcase component to streamline the showcase and focus on the Tertiary variant. Updated Button component to support the new variant and adjusted related styles in the CSS for both green and black themes.
This commit is contained in:
akcodez
2025-12-01 14:07:29 -08:00
parent 73b2127f87
commit f20177b5f9
9 changed files with 1345 additions and 1822 deletions

View File

@@ -0,0 +1,561 @@
import * as React from 'react';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
export const frontmatter = {
seo: {
title: 'BDS Tertiary Button Component Showcase',
description: 'Interactive showcase of the Brand Design System Tertiary Button component with all states and variants.',
},
};
export default function ButtonShowcaseTertiary() {
const [clickCount, setClickCount] = React.useState(0);
const handleClick = () => {
setClickCount((prev) => prev + 1);
};
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">BDS Tertiary Button</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
The Tertiary button is a text-only button style used for low-emphasis or contextual actions. It features no
background fill or border, appearing as a simple text link with optional arrow icon. This variant provides the
lowest visual emphasis while maintaining brand consistency through green text colors.
</p>
</section>
{/* Basic Usage */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Basic Usage</h2>
<h6 className="eyebrow mb-3">Tertiary Variant</h6>
</div>
<div className="d-flex flex-wrap align-items-center">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
View Details
</Button>
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
Learn More
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
Read More
</Button>
</div>
{clickCount > 0 && (
<p className="mt-4 text-muted">
Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}
</p>
)}
</section>
{/* Primary vs Secondary vs Tertiary Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Primary vs Secondary vs Tertiary</h2>
<h6 className="eyebrow mb-3">Visual Hierarchy</h6>
</div>
<p className="mb-4 text-muted">
Use Primary for main actions, Secondary for supporting actions, and Tertiary for low-emphasis or contextual
actions to create clear visual hierarchy.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Get Started
</Button>
<Button variant="secondary" onClick={handleClick} className="me-4 mb-4">
Learn More
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
View Details
</Button>
</div>
</section>
{/* States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Text-only style with green text color, no background or border.</p>
<Button variant="tertiary" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Gray text indicates non-interactive state. Icon is hidden.</p>
<Button variant="tertiary" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the underline appears and text color darkens to
Green 500. The focus state adds a green outline around the text.
</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Black Color Variant */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Color Variant</h2>
<h6 className="eyebrow mb-3">Color Theme</h6>
</div>
<p className="mb-4 text-muted">
Tertiary buttons can use a black color theme with black text instead of green.
</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
Black Tertiary
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
View Details
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="mb-4">
Learn More
</Button>
</div>
</section>
{/* Black Variant States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Black Variant States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Black text with transparent background.</p>
<Button variant="tertiary" color="black" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Same disabled styling as green variant.</p>
<Button variant="tertiary" color="black" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons or use Tab to focus them. Notice the underline appears on hover/focus.
</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" color="black" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="mb-4">
Focus Me (Tab)
</Button>
</div>
</div>
</section>
{/* Green vs Black Comparison */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Green vs Black Comparison</h2>
<h6 className="eyebrow mb-3">Color Themes</h6>
</div>
<p className="mb-4 text-muted">Compare the green (default) and black color themes side by side.</p>
<div className="d-flex flex-wrap align-items-center">
<Button variant="tertiary" color="green" onClick={handleClick} className="me-4 mb-4">
Green Tertiary
</Button>
<Button variant="tertiary" color="black" onClick={handleClick} className="mb-4">
Black Tertiary
</Button>
</div>
</section>
{/* Without Icon */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Without Icon</h2>
<h6 className="eyebrow mb-3">Icon Control</h6>
</div>
<p className="mb-4 text-muted">Tertiary buttons can also be rendered without the arrow icon.</p>
<div className="d-flex flex-wrap">
<Button variant="tertiary" showIcon={false} onClick={handleClick} className="me-4 mb-4">
No Icon Button
</Button>
<Button variant="tertiary" showIcon={true} onClick={handleClick} className="mb-4">
With Icon Button
</Button>
</div>
</section>
{/* Button Types */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button Types</h2>
<h6 className="eyebrow mb-3">Form Integration</h6>
</div>
<p className="mb-4 text-muted">Tertiary buttons can be used for form actions like cancel or reset.</p>
<form
onSubmit={(e) => {
e.preventDefault();
alert('Form submitted!');
}}
className="d-flex flex-wrap"
>
<Button variant="primary" type="submit" className="me-4 mb-4">
Submit
</Button>
<Button variant="tertiary" type="reset" className="me-4 mb-4">
Reset
</Button>
<Button variant="tertiary" type="button" onClick={() => alert('Cancelled!')} className="mb-4">
Cancel
</Button>
</form>
</section>
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-4 text-muted">
Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes:
</p>
<ul className="mb-4">
<li>
<strong>Desktop (1024px):</strong> Padding: 8px 20px, Gap: 16px
</li>
<li>
<strong>Tablet/Mobile (1023px):</strong> Padding: 8px 16px, Gap: 16px
</li>
<li>
<strong>Hover/Focus:</strong> Gap increases (22px desktop, 21px mobile) with adjusted padding
</li>
</ul>
<div className="d-flex flex-wrap">
<Button variant="tertiary" onClick={handleClick} className="me-4 mb-4">
Responsive Button
</Button>
<Button variant="tertiary" onClick={handleClick} className="mb-4">
Long Button Label Example
</Button>
</div>
</section>
{/* Accessibility */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Accessibility Features</h2>
<h6 className="eyebrow mb-3">WCAG Compliance</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px green outline (Green 500)</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Color Contrast</h5>
<ul>
<li>
<strong>Enabled:</strong> Green 400 (#0DAA3E) on White = 4.52:1 (AA for large text)
</li>
<li>
<strong>Hover/Focus:</strong> Green 500 (#078139) on White = 5.12:1 (AA)
</li>
<li>
<strong>Disabled:</strong> Gray 400 (#A2A2A4) on White = reduced contrast (acceptable for disabled state)
</li>
</ul>
</div>
</section>
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
// Basic tertiary button (green theme - default)
<Button variant="tertiary" onClick={handleClick}>
View Details
</Button>
// Black color theme
<Button variant="tertiary" color="black" onClick={handleClick}>
View Details
</Button>
// Disabled state
<Button variant="tertiary" disabled>
Unavailable
</Button>
// Without icon
<Button variant="tertiary" showIcon={false}>
Cancel
</Button>
// Primary + Secondary + Tertiary pairing
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="secondary" type="button">
Learn More
</Button>
<Button variant="tertiary" type="button">
Cancel
</Button>`}</code>
</pre>
</div>
</section>
{/* Design Specifications */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Design Specifications</h2>
<h6 className="eyebrow mb-3">Visual Details</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 18px (Body R token - different from Primary/Secondary)</li>
<li>Weight: 400</li>
<li>Line Height: 26.1px</li>
<li>Letter Spacing: -0.5px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded - inherited but not visually apparent)</li>
<li>Border: None</li>
<li>Background: Transparent</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Max Height: 40px</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors - Green Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Decoration</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#078139 (Green 500)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline + 2px Green 500 outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#0DAA3E (Green 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
<div className="mt-10">
<h5 className="mb-4">State Colors - Black Theme</h5>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>State</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Color</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Background</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Text Decoration</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Enabled</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Hover</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline + 2px Black outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Active</div>
<div style={{ padding: '12px' }}>#141414 (Neutral Black)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Underline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Disabled</div>
<div style={{ padding: '12px' }}>#A2A2A4 (Gray 400)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>None</div>
</div>
</div>
</div>
</section>
{/* Key Differences from Primary/Secondary */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Key Differences from Primary/Secondary</h2>
<h6 className="eyebrow mb-3">Comparison</h6>
</div>
<div style={{ width: '100%', backgroundColor: '#FFFFFF' }}>
{/* Header */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '2px solid #E0E0E1' }}>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Aspect</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Primary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Secondary</div>
<div style={{ padding: '12px', fontWeight: 'bold' }}>Tertiary</div>
</div>
{/* Rows */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Enabled)</div>
<div style={{ padding: '12px' }}>Green 300 (#21E46B)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Background (Hover)</div>
<div style={{ padding: '12px' }}>Green 200 (#70EE97)</div>
<div style={{ padding: '12px' }}>Green 100 (#EAFCF1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Border (Enabled)</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>2px Green 400</div>
<div style={{ padding: '12px' }}>None</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Color (Enabled)</div>
<div style={{ padding: '12px' }}>Black (#141414)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
<div style={{ padding: '12px' }}>Green 400 (#0DAA3E)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Text Decoration</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>None</div>
<div style={{ padding: '12px' }}>Underline (hover/focus/active)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Typography Token</div>
<div style={{ padding: '12px' }}>Label R (16px)</div>
<div style={{ padding: '12px' }}>Label R (16px)</div>
<div style={{ padding: '12px' }}>Body R (18px)</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Focus Indicator</div>
<div style={{ padding: '12px' }}>2px Black border</div>
<div style={{ padding: '12px' }}>2px Black outline</div>
<div style={{ padding: '12px' }}>2px Green 500 outline</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', borderBottom: '1px solid #E0E0E1' }}>
<div style={{ padding: '12px' }}>Disabled Background</div>
<div style={{ padding: '12px' }}>Gray 200 (#E0E0E1)</div>
<div style={{ padding: '12px' }}>Transparent</div>
<div style={{ padding: '12px' }}>Transparent</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
<div style={{ padding: '12px' }}>Arrow Icon</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
<div style={{ padding: '12px' }}> Shared</div>
</div>
</div>
</section>
</div>
);
}

View File

@@ -1,226 +0,0 @@
# Button Component Documentation
## Overview
Buttons are clickable elements used to trigger actions or guide users toward key interactions. They serve as clear calls to action within the interface, allowing users to engage with a page in various ways. Button labels should clearly describe the action that will occur when the button is clicked.
---
## When to Use
Use buttons to represent actions that users can take on a page or within a component. Buttons communicate intent and encourage interaction. Each view should include only one primary button to clearly indicate the main action. Any secondary or supporting actions should be represented using lower-emphasis button styles such as secondary or tertiary.
---
## When Not to Use
Avoid using buttons for navigation. If the action takes the user to a new page or external destination, use a link instead. Buttons should initiate actions, not navigation, to maintain consistency and clarity in user expectations.
---
## Variants
Each button variant has a particular function, and its design visually communicates that function to the user. It is important that these variants are implemented consistently across XRPL.org to maintain clarity and hierarchy in user actions.
| Variant | Emphasis Level | Style | Purpose / Use Case |
|---------|----------------|-------|-------------------|
| **Primary** | High | Filled green on light backgrounds, Filled black on color backgrounds | The main call to action on a page. Used for primary tasks such as "Get Started" or "Submit." Only one primary button should appear per view. |
| **Secondary** | Medium | Outline (green stroke, transparent fill) | A supporting action that appears alongside a primary button, such as "Learn More" or "Back." Provides a secondary path without competing visually with the primary action. |
| **Tertiary** | Low | Text-only (green text, no border or fill) | A low-emphasis or contextual action, often used inline or in less prominent areas such as "View Details" or "Cancel." |
---
## Anatomy
Buttons consist of the following elements:
1. **Label** - Clear, descriptive text indicating the action
2. **Container** - Background fill or stroke defining the button boundary
3. **Icon** (optional) - Visual indicator supporting the label
4. **Padding** - Internal spacing maintaining touch targets and visual proportion
---
## Sizing
Buttons follow consistent sizing rules across breakpoints, defined by internal padding, spacing, and typography. Primary and Secondary buttons share the same type token, while Tertiary buttons use a lighter text style appropriate for text-only interactions.
Padding adjusts per breakpoint to maintain appropriate touch targets and visual proportion.
### Sizing Specifications
| Breakpoint | Horizontal Padding Left | Horizontal Padding Right | Vertical Padding | Label - Icon Spacing | Type Token (Primary + Secondary) | Type Token (Tertiary) |
|------------|-------------------------|--------------------------|------------------|----------------------|----------------------------------|----------------------|
| **Desktop** | 20px | 19px | 8px | 16px | Label R | Body R |
| **Tablet** | 16px | 15px | 8px | 16px | Label R | Body R |
| **Mobile** | 16px | 15px | 8px | 16px | Label R | Body R |
---
## Color
Buttons use two primary color themes to maintain contrast and clarity across different backgrounds. Green is the default theme across the system, with black used only when the button sits on a green background. Dark Mode uses reversed green values for accessibility and consistency, Black theme also turns to dark green set.
### Theme Guidelines
| Theme | When to Use | Purpose |
|-------|-------------|---------|
| **Green Theme (Default)** | Used on all backgrounds except Green | Primary branded appearance for the Overflow Menu UI |
| **Black Theme** | Used only when the background is Green | Prevents green-on-green blending and maintains visibility |
### Background-Based Theme Behavior
| Background Type | Theme Behavior | Visual Notes |
|----------------|----------------|--------------|
| Light backgrounds (white, light gray, neutral) | Use Green Theme, unless on Green | Text and icons remain dark, green theme maintains brand clarity |
| Green Background | Switch to Black Theme | Ensures contrast and avoids color clash |
| Dark Backgrounds (black, deep surfaces) | Both themes goes to Green dark mode | Backgrounds brightened to remain accessible |
---
## Behaviors
Buttons respond to user interactions through the following states:
### State Overview
- **Enabled** - Default resting state
- **Hover** - Mouse cursor over button
- **Focused** - Keyboard focus or active focus state
- **Active** - Button being pressed/clicked
- **Disabled** - Button cannot be interacted with
### Primary Button - Light Mode
| State | Text | Background | Stroke |
|-------|------|------------|--------|
| Enabled | Neutral Black | Green 300 | None |
| Hover | Neutral Black | Green 200 | None |
| Focused | Neutral Black | Green 200 | Black 2px |
| Active | Neutral Black | Green 300 | None |
| Disabled | Neutral 500 | Neutral 200 | None |
### Primary Button - Dark Mode
| State | Text | Background | Stroke |
|-------|------|------------|--------|
| Enabled | Neutral Black | Green 300 | None |
| Hover | Neutral Black | Green 200 | None |
| Focused | Neutral Black | Green 200 | White 2px |
| Active | Neutral Black | Green 300 | None |
| Disabled | Neutral 300 | Neutral 500 | None |
### Secondary Button - Light Mode
| State | Text | Background | Stroke |
|-------|------|------------|--------|
| Enabled | Green 400 | Transparent | Green 400 2px |
| Hover | Green 500 | Transparent | Green 500 2px |
| Focused | Green 500 | Transparent | Green 500 2px |
| Active | Green 400 | Transparent | Green 400 2px |
| Disabled | Neutral 400 | Transparent | Neutral 400 2px |
### Secondary Button - Dark Mode
| State | Text | Background | Stroke |
|-------|------|------------|--------|
| Enabled | Green 300 | Transparent | Green 300 2px |
| Hover | Green 200 | Transparent | Green 200 2px |
| Focused | Green 200 | Transparent | Green 200 2px |
| Active | Green 300 | Transparent | Green 300 2px |
| Disabled | Neutral 400 | Transparent | Neutral 400 2px |
### Tertiary Button - Light Mode
| State | Text | Text Decoration |
|-------|------|-----------------|
| Enabled | Green 400 | None |
| Hover | Green 500 | Underline |
| Focused | Green 500 | Underline |
| Active | Green 400 | Underline |
| Disabled | Neutral 400 | None |
### Tertiary Button - Dark Mode
| State | Text | Text Decoration |
|-------|------|-----------------|
| Enabled | Green 300 | None |
| Hover | Green 200 | Underline |
| Focused | Green 200 | Underline |
| Active | Green 300 | Underline |
| Disabled | Neutral 400 | None |
---
## Best Practices
### Do's ✓
- Use clear, action-oriented labels (e.g., "Submit", "Download", "Learn More")
- Limit to one primary button per view
- Ensure sufficient color contrast for accessibility
- Maintain consistent sizing across breakpoints
- Use appropriate button variant for the action hierarchy
### Don'ts ✗
- Don't use buttons for navigation (use links instead)
- Don't use vague labels like "Click Here" or "OK"
- Don't stack multiple primary buttons
- Don't use green buttons on green backgrounds
- Don't make disabled buttons look clickable
---
## Accessibility
- Buttons must meet WCAG 2.1 AA contrast ratios (4.5:1 for text)
- All buttons must be keyboard accessible
- Focus states must be clearly visible
- Disabled buttons should be indicated via aria-disabled attribute
- Button labels should be concise and descriptive
- Touch targets should be minimum 44x44px on mobile
---
## Design Tokens
### Typography
- **Label R**: Font size 16px, Line height 23.2px, Letter spacing 0px
- **Body R**: Font size 18px, Line height 26.1px, Letter spacing -0.5px
### Colors - Green Theme
- **Green 200**: `#70ee97`
- **Green 300**: `#21e46b` (Default)
- **Green 400**: `#0daa3e`
- **Green 500**: `#078139`
### Colors - Neutral
- **Neutral Black**: `#141414`
- **Neutral White**: `#ffffff`
- **Neutral 200**: `#e6eaf0`
- **Neutral 300**: `#cad4df`
- **Neutral 400**: `#8a919a`
- **Neutral 500**: `#72777e`
### Spacing
- **Border Radius**: 100px (fully rounded)
- **Border Width**: 2px
- **Icon Spacing**: 16px
---
## Implementation Notes
1. Buttons should use the `Booton` font family
2. Icons should scale proportionally with button size
3. Internal link buttons should use the appropriate link icon
4. All states should have smooth transitions (recommended 150ms ease)
5. Ensure proper touch target sizes on mobile devices
6. Test color contrast in both light and dark modes
---
## Version History
- **Current Version**: 1.0
- **Last Updated**: December 1, 2025
- **Source**: [Figma Design File](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5004-1245&m=dev)

View File

@@ -1,737 +0,0 @@
# Primary Button Logic Documentation
## Overview
This document defines the implementation logic for the **Primary Button** component, which represents the highest emphasis call-to-action in the interface. Primary buttons should be used sparingly—only one per view—to clearly indicate the main action.
---
## Button States & Visual Specifications
### 1. Enabled (Default State)
The resting state when the button is ready for interaction.
**Visual Properties:**
- **Text Color**: `#141414` (Neutral Black)
- **Background Color**: `$green-300` (`#21E46B`)
- **Border**: None
- **Icon**: `/static/img/icons/button/button-arrow-right.svg`
```scss
.btn-primary {
color: #141414; // Neutral Black
background-color: $green-300; // #21E46B
border: none;
border-radius: 100px;
padding: 8px 19px 8px 20px;
font-size: 16px;
font-weight: 400;
letter-spacing: 0px;
line-height: 23.2px;
display: flex;
align-items: center;
gap: 16px;
cursor: pointer;
}
```
---
### 2. Hover State
Triggered when the user's cursor hovers over the button.
**Visual Properties:**
- **Text Color**: `#141414` (Neutral Black)
- **Background Color**: `$green-200` (`#70EE97`)
- **Border**: None
- **Icon**: `/static/img/icons/button/button-arrow-hovered.svg`
- **Transition**: Smooth background color and icon change
```scss
.btn-primary:hover {
color: #141414; // Neutral Black
background-color: $green-200; // #70EE97
.btn-icon {
// Swap to hovered arrow icon
background-image: url('/static/img/icons/button/button-arrow-hovered.svg');
}
}
```
---
### 3. Focus State
Triggered when the button receives keyboard focus or is actively focused.
**Visual Properties:**
- **Text Color**: `#141414` (Neutral Black)
- **Background Color**: `$green-200` (`#70EE97`)
- **Border**: `2px solid #141414` (Black)
- **Border Radius**: `100px`
- **Icon**: `/static/img/icons/button/button-arrow-hovered.svg`
```scss
.btn-primary:focus,
.btn-primary:focus-visible {
color: #141414; // Neutral Black
background-color: $green-200; // #70EE97
border: 2px solid #141414; // Black outline for keyboard navigation
outline: none;
.btn-icon {
// Swap to hovered arrow icon
background-image: url('/static/img/icons/button/button-arrow-hovered.svg');
}
}
```
---
### 4. Active State
Triggered when the button is being clicked or pressed.
**Visual Properties:**
- **Text Color**: `#141414` (Neutral Black)
- **Background Color**: `$green-300` (`#21E46B`)
- **Border**: None
- **Icon**: `/static/img/icons/button/button-arrow-right.svg`
- **Visual Feedback**: Optional slight scale or pressed effect
```scss
.btn-primary:active {
color: #141414; // Neutral Black
background-color: $green-300; // #21E46B (same as enabled)
transform: scale(0.98); // Optional: slight press effect
.btn-icon {
background-image: url('/static/img/icons/button/button-arrow-right.svg');
}
}
```
---
### 5. Disabled State
When the button cannot be interacted with due to conditions or permissions.
**Visual Properties:**
- **Text Color**: `$gray-500` (`#838386` - Neutral 500)
- **Background Color**: `$gray-200` (`#E0E0E1` - Neutral 200)
- **Border**: None
- **Icon**: `/static/img/icons/button/button-arrow-right.svg` (grayed out)
- **Cursor**: `not-allowed`
- **Pointer Events**: Disabled
```scss
.btn-primary:disabled,
.btn-primary[disabled] {
color: $gray-500; // #838386 (Neutral 500)
background-color: $gray-200; // #E0E0E1 (Neutral 200)
cursor: not-allowed;
pointer-events: none;
opacity: 1; // Full opacity, color defines disabled state
.btn-icon {
opacity: 0.5; // Dim the icon
}
}
```
---
## Icon Implementation
### Arrow Icons
The primary button uses directional arrow icons to indicate forward action:
- **Default/Enabled**: `/static/img/icons/button/button-arrow-right.svg`
- **Hover/Focus**: `/static/img/icons/button/button-arrow-hovered.svg`
- **Active**: `/static/img/icons/button/button-arrow-right.svg`
- **Disabled**: `/static/img/icons/button/button-arrow-right.svg` (with reduced opacity)
### Icon Spacing
- **Gap between label and icon**: `16px`
- **Icon size**: `14px × 15px` (internal small icon size)
---
## Typography
### Font Specifications
Based on the **Label R** type token:
```scss
.btn-primary {
font-family: 'Booton', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 23.2px;
letter-spacing: 0px;
}
```
---
## Layout & Spacing
### Sizing
- **Min-height**: `40px` (to ensure adequate touch target)
- **Min-width**: Auto (based on content)
- **Max-width**: None (fluid to content)
- **Border Radius**: `100px` (fully rounded corners)
---
## Responsive Padding Specifications
Primary button padding adjusts across breakpoints and states to maintain visual balance and accommodate icon spacing changes. The padding system is designed to ensure consistent touch targets and optical alignment.
### Design Rationale
The primary button uses **asymmetric padding** to account for:
1. **Visual weight balance** - Compensates for right-aligned icon
2. **Icon spacing dynamics** - Hover state increases icon gap, reducing right padding
3. **Touch target consistency** - Maintains minimum 40px height across all states
### Breakpoint-Based Padding
#### Desktop (≥1024px)
| State | Padding (T/R/B/L) | Icon Gap | Total Width Calculation |
|-------|-------------------|----------|-------------------------|
| **Enabled/Active** | `8px 19px 8px 20px` | `16px` | Left(20px) + Label + Gap(16px) + Icon(15px) + Right(19px) |
| **Hover/Focus** | `8px 13px 8px 20px` | `22px` | Left(20px) + Label + Gap(22px) + Icon(15px) + Right(13px) |
```scss
// Desktop - Default/Enabled/Active states
.btn-primary {
padding: 8px 19px 8px 20px;
gap: 16px;
// Desktop - Hover/Focus states
&:hover,
&:focus-visible {
padding: 8px 13px 8px 20px;
gap: 22px;
// Note: Right padding reduces by 6px while gap increases by 6px
// This maintains total button width during state transitions
}
}
```
**Key Insight**: The 6px reduction in right padding exactly compensates for the 6px increase in icon gap, preventing layout shifts during hover transitions.
---
#### Tablet (768px - 1023px)
| State | Padding (T/R/B/L) | Icon Gap | Total Width Calculation |
|-------|-------------------|----------|-------------------------|
| **Enabled/Active** | `8px 15px 8px 16px` | `16px` | Left(16px) + Label + Gap(16px) + Icon(15px) + Right(15px) |
| **Hover/Focus** | `8px 10px 8px 16px` | `21px` | Left(16px) + Label + Gap(21px) + Icon(15px) + Right(10px) |
```scss
// Tablet - Default/Enabled/Active states
@media (min-width: 768px) and (max-width: 1023px) {
.btn-primary {
padding: 8px 15px 8px 16px;
gap: 16px;
// Tablet - Hover/Focus states
&:hover,
&:focus-visible {
padding: 8px 10px 8px 16px;
gap: 21px;
// Right padding reduces by 5px while gap increases by 5px
}
}
}
```
---
#### Mobile (≤767px)
| State | Padding (T/R/B/L) | Icon Gap | Total Width Calculation |
|-------|-------------------|----------|-------------------------|
| **Enabled/Active** | `8px 15px 8px 16px` | `16px` | Left(16px) + Label + Gap(16px) + Icon(15px) + Right(15px) |
| **Hover/Focus** | `8px 10px 8px 16px` | `21px` | Left(16px) + Label + Gap(21px) + Icon(15px) + Right(10px) |
```scss
// Mobile - Default/Enabled/Active states
@media (max-width: 767px) {
.btn-primary {
padding: 8px 15px 8px 16px;
gap: 16px;
// Mobile - Hover/Focus states (same as tablet)
&:hover,
&:focus-visible {
padding: 8px 10px 8px 16px;
gap: 21px;
}
}
}
```
**Note**: Mobile and Tablet share identical padding values to maintain consistency across smaller viewports.
---
### Special State Considerations
#### Focus State with Border
When the focus state adds a 2px border, padding must be adjusted to prevent layout shift:
```scss
.btn-primary:focus-visible {
border: 2px solid #141414;
// Desktop - Compensate for 2px border
padding: 6px 11px 6px 18px; // Reduced by 2px on all sides
gap: 22px; // Maintains hover gap
// Tablet & Mobile - Compensate for 2px border
@media (max-width: 1023px) {
padding: 6px 8px 6px 14px; // Reduced by 2px on all sides
gap: 21px;
}
}
```
#### Disabled State
Disabled state uses the same padding as the enabled state but does not transition:
```scss
.btn-primary:disabled {
padding: 8px 19px 8px 20px; // Desktop
gap: 16px;
// No hover padding changes apply
@media (max-width: 1023px) {
padding: 8px 15px 8px 16px; // Tablet & Mobile
}
}
```
---
### Padding Transition Strategy
To ensure smooth state transitions without visual "jumps":
```scss
.btn-primary {
transition-property: padding, gap, background-color, border-color;
transition-duration: 150ms;
transition-timing-function: ease-in-out;
}
```
### Visual Alignment Formula
The padding system maintains optical centering using this formula:
```
Total Button Width = Left Padding + Label Width + Icon Gap + Icon Width + Right Padding
Optical Center Offset = (Left Padding - Right Padding) / 2
```
**Example (Desktop Enabled):**
- Left: 20px, Right: 19px → Offset: 0.5px (virtually centered)
**Example (Desktop Hover):**
- Left: 20px, Right: 13px → Offset: 3.5px (compensates for larger icon gap)
---
### Implementation Summary
```scss
@import 'styles/_colors.scss';
.btn-primary {
// Desktop base
padding: 8px 19px 8px 20px;
gap: 16px;
border-radius: 100px;
transition: padding 150ms ease-in-out, gap 150ms ease-in-out;
// Desktop hover/focus
&:hover:not(:disabled),
&:focus-visible:not(:disabled) {
padding: 8px 13px 8px 20px;
gap: 22px;
}
// Focus with border
&:focus-visible:not(:disabled) {
border: 2px solid #141414;
padding: 6px 11px 6px 18px; // Compensate for border
}
// Tablet & Mobile
@media (max-width: 1023px) {
padding: 8px 15px 8px 16px;
gap: 16px;
&:hover:not(:disabled),
&:focus-visible:not(:disabled) {
padding: 8px 10px 8px 16px;
gap: 21px;
}
&:focus-visible:not(:disabled) {
border: 2px solid #141414;
padding: 6px 8px 6px 14px;
}
}
}
```
---
### Quick Reference Table
| Breakpoint | State | Top | Right | Bottom | Left | Gap |
|------------|-------|-----|-------|--------|------|-----|
| **Desktop** | Enabled/Active | 8px | 19px | 8px | 20px | 16px |
| **Desktop** | Hover | 8px | 13px | 8px | 20px | 22px |
| **Desktop** | Focus (w/ border) | 6px | 11px | 6px | 18px | 22px |
| **Tablet** | Enabled/Active | 8px | 15px | 8px | 16px | 16px |
| **Tablet** | Hover | 8px | 10px | 8px | 16px | 21px |
| **Tablet** | Focus (w/ border) | 6px | 8px | 6px | 14px | 21px |
| **Mobile** | Enabled/Active | 8px | 15px | 8px | 16px | 16px |
| **Mobile** | Hover | 8px | 10px | 8px | 16px | 21px |
| **Mobile** | Focus (w/ border) | 6px | 8px | 6px | 14px | 21px |
---
## Color Reference (from _colors.scss)
### Green Palette
```scss
$green-100: #EAFCF1; // Lightest green
$green-200: #70EE97; // Hover state
$green-300: #21E46B; // Default/Enabled state
$green-400: #0DAA3E; // Darker green
$green-500: #078139; // Darkest green
```
### Neutral Palette
```scss
$gray-200: #E0E0E1; // Disabled background (Neutral 200)
$gray-500: #838386; // Disabled text (Neutral 500)
$black: #141414; // Primary text color (Neutral Black)
```
---
## State Transition Logic
### State Priority (Highest to Lowest)
1. **Disabled** - Overrides all other states
2. **Active** - During click/press
3. **Focus** - Keyboard navigation or explicit focus
4. **Hover** - Mouse over
5. **Enabled** - Default resting state
### Transition Timing
```scss
.btn-primary {
transition: all 150ms ease-in-out;
&:not(:disabled) {
// Only apply transitions to interactive states
transition-property: background-color, border-color, transform;
}
}
```
---
## Accessibility Requirements
### WCAG Compliance
- **Color Contrast**:
- Enabled state: Black text (#141414) on Green 300 (#21E46B) = **9.06:1** ✓ (AAA)
- Hover state: Black text (#141414) on Green 200 (#70EE97) = **10.23:1** ✓ (AAA)
- Disabled state: Gray 500 text (#838386) on Gray 200 (#E0E0E1) = **2.12:1** ⚠️ (Passes for disabled)
### Keyboard Navigation
```scss
.btn-primary:focus-visible {
// 2px black border ensures keyboard focus is clearly visible
border: 2px solid #141414;
outline: none; // Remove default browser outline
}
```
### Screen Readers
```html
<button class="btn-primary" type="button">
<span class="btn-label">Submit Form</span>
<img src="/static/img/icons/button/button-arrow-right.svg"
class="btn-icon"
alt=""
aria-hidden="true" />
</button>
<!-- Disabled state -->
<button class="btn-primary" type="button" disabled aria-disabled="true">
<span class="btn-label">Submit Form</span>
<img src="/static/img/icons/button/button-arrow-right.svg"
class="btn-icon"
alt=""
aria-hidden="true" />
</button>
```
---
## Implementation Example (React + SCSS)
### Component Structure
```tsx
import React from 'react';
import './PrimaryButton.scss';
interface PrimaryButtonProps {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
className?: string;
}
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
children,
onClick,
disabled = false,
type = 'button',
className = '',
}) => {
const [isHovered, setIsHovered] = React.useState(false);
const [isFocused, setIsFocused] = React.useState(false);
const getArrowIcon = () => {
if (disabled) {
return '/static/img/icons/button/button-arrow-right.svg';
}
return isHovered || isFocused
? '/static/img/icons/button/button-arrow-hovered.svg'
: '/static/img/icons/button/button-arrow-right.svg';
};
return (
<button
type={type}
className={`btn-primary ${className}`}
onClick={onClick}
disabled={disabled}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
aria-disabled={disabled}
>
<span className="btn-label">{children}</span>
<img
src={getArrowIcon()}
className="btn-icon"
alt=""
aria-hidden="true"
/>
</button>
);
};
```
### SCSS Styles
```scss
@import 'styles/_colors.scss';
.btn-primary {
// Base styles
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 100px;
border: none;
cursor: pointer;
white-space: nowrap;
// Typography
font-family: 'Booton', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 23.2px;
letter-spacing: 0px;
// Desktop padding and gap (default)
padding: 8px 19px 8px 20px;
gap: 16px;
// Enabled state colors (default)
color: #141414; // Neutral Black
background-color: $green-300; // #21E46B
// Transitions
transition: all 150ms ease-in-out;
transition-property: background-color, border-color, transform, padding, gap;
// Hover state
&:hover:not(:disabled) {
background-color: $green-200; // #70EE97
padding: 8px 13px 8px 20px; // Adjust for icon gap change
gap: 22px; // Increased icon spacing
}
// Focus state
&:focus-visible:not(:disabled) {
background-color: $green-200; // #70EE97
border: 2px solid #141414; // Black focus ring
outline: none;
// Adjust padding to compensate for border and gap
padding: 6px 11px 6px 18px;
gap: 22px;
}
// Active state
&:active:not(:disabled) {
background-color: $green-300; // #21E46B
transform: scale(0.98);
// Maintains default padding and gap
}
// Disabled state
&:disabled,
&[disabled] {
color: $gray-500; // #838386 (Neutral 500)
background-color: $gray-200; // #E0E0E1 (Neutral 200)
cursor: not-allowed;
pointer-events: none;
// Maintains default padding and gap
.btn-icon {
opacity: 0.5;
}
}
// Tablet & Mobile responsive padding
@media (max-width: 1023px) {
padding: 8px 15px 8px 16px;
gap: 16px;
&:hover:not(:disabled) {
padding: 8px 10px 8px 16px;
gap: 21px;
}
&:focus-visible:not(:disabled) {
padding: 6px 8px 6px 14px; // Compensate for border
gap: 21px;
}
}
// Icon styles
.btn-icon {
width: 15px;
height: 14px;
flex-shrink: 0;
transition: opacity 150ms ease-in-out;
}
.btn-label {
flex-shrink: 0;
}
}
```
---
## Usage Guidelines
### Do's ✓
- Use only **one primary button per view** to indicate the main action
- Use clear, action-oriented labels (e.g., "Submit Form", "Get Started", "Continue")
- Place primary buttons in prominent locations aligned with user flow
- Ensure sufficient spacing around the button for easy interaction
- Test keyboard navigation and screen reader compatibility
### Don'ts ✗
- Don't use multiple primary buttons on the same page/view
- Don't use vague labels like "Click Here" or "OK"
- Don't use primary buttons for navigation (use links instead)
- Don't override disabled state colors for "creative" reasons
- Don't remove the focus indicator for keyboard accessibility
---
## Testing Checklist
### Visual States
- [ ] Enabled state displays correctly with Green 300 background
- [ ] Hover state transitions to Green 200 background
- [ ] Focus state shows 2px black border outline
- [ ] Active state provides visual feedback on click
- [ ] Disabled state is not interactive and displays grayed out
- [ ] Arrow icon swaps between states correctly
### Responsive Padding
- [ ] Desktop padding: 8px 19px 8px 20px (enabled/active)
- [ ] Desktop hover padding: 8px 13px 8px 20px with 22px gap
- [ ] Tablet/Mobile padding: 8px 15px 8px 16px (enabled/active)
- [ ] Tablet/Mobile hover padding: 8px 10px 8px 16px with 21px gap
- [ ] Focus state padding compensates for 2px border on all breakpoints
- [ ] Button width remains consistent during hover transitions
- [ ] No layout shift when transitioning between states
### Accessibility
- [ ] Keyboard navigation works (Tab to focus, Enter/Space to activate)
- [ ] Screen reader announces button label and disabled state
- [ ] Button works on touch devices (minimum 44x44px touch target)
- [ ] Color contrast meets WCAG AA standards
- [ ] Focus indicator is clearly visible on all backgrounds
---
## Version History
- **Version**: 1.1
- **Last Updated**: December 1, 2025
- **Changelog**:
- v1.1: Added comprehensive responsive padding specifications section
- v1.0: Initial release with button states and visual specifications
- **Design Sources**:
- [Figma - Button States](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5226-8948&m=dev)
- [Figma - Button Sizing & Padding](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5226-9301&m=dev)
- **Color Reference**: `styles/_colors.scss`
- **Icon Assets**:
- `/static/img/icons/button/button-arrow-right.svg`
- `/static/img/icons/button/button-arrow-hovered.svg`

View File

@@ -1,355 +0,0 @@
import * as React from 'react';
import { Button } from 'shared/components/Button';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
export const frontmatter = {
seo: {
title: 'BDS Button Component Showcase',
description: 'Interactive showcase of the Brand Design System Button component with all states and variants.',
},
};
export default function ButtonShowcase() {
const [clickCount, setClickCount] = React.useState(0);
const [isLoading, setIsLoading] = React.useState(false);
const handleClick = () => {
setClickCount((prev) => prev + 1);
};
const handleAsyncClick = async () => {
setIsLoading(true);
await new Promise((resolve) => setTimeout(resolve, 2000));
setIsLoading(false);
setClickCount((prev) => prev + 1);
};
return (
<div className="landing">
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-lg-8 mx-auto">
<h1 className="mb-0">BDS Button Component</h1>
<h6 className="eyebrow mb-3">Brand Design System</h6>
</div>
<p className="col-lg-8 mx-auto mt-10">
A scalable button component following the XRPL Brand Design System. This showcase demonstrates all states,
responsive behavior, and accessibility features of the Primary button variant.
</p>
</section>
{/* Basic Usage */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Basic Usage</h2>
<h6 className="eyebrow mb-3">Primary Variant</h6>
</div>
<div className="d-flex flex-wrap align-items-center">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Get Started
</Button>
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Submit Form
</Button>
<Button variant="primary" onClick={handleClick} className="mb-4">
Continue
</Button>
</div>
{clickCount > 0 && (
<p className="mt-4 text-muted">Button clicked {clickCount} time{clickCount !== 1 ? 's' : ''}</p>
)}
</section>
{/* States */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button States</h2>
<h6 className="eyebrow mb-3">Interactive States</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Enabled State</h5>
<p className="mb-4 text-muted">Default state when button is ready for interaction.</p>
<Button variant="primary" onClick={handleClick}>
Enabled Button
</Button>
</div>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#f5f5f7' }}>
<h5 className="mb-4">Disabled State</h5>
<p className="mb-4 text-muted">Button cannot be interacted with.</p>
<Button variant="primary" disabled>
Disabled Button
</Button>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Hover & Focus States</h5>
<p className="mb-4 text-muted">
Hover over the buttons below or use Tab to focus them. Notice the background color change and icon swap.
</p>
<div className="d-flex flex-wrap">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Hover Me
</Button>
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Focus Me (Tab)
</Button>
<Button variant="primary" onClick={handleClick} className="mb-4">
Active State (Click)
</Button>
</div>
</div>
</section>
{/* Without Icon */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Without Icon</h2>
<h6 className="eyebrow mb-3">Icon Control</h6>
</div>
<p className="mb-4 text-muted">Buttons can be rendered without the arrow icon when needed.</p>
<div className="d-flex flex-wrap">
<Button variant="primary" showIcon={false} onClick={handleClick} className="me-4 mb-4">
No Icon Button
</Button>
<Button variant="primary" showIcon={true} onClick={handleClick} className="mb-4">
With Icon Button
</Button>
</div>
</section>
{/* Button Types */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Button Types</h2>
<h6 className="eyebrow mb-3">Form Integration</h6>
</div>
<p className="mb-4 text-muted">Different button types for form submission and actions.</p>
<form
onSubmit={(e) => {
e.preventDefault();
alert('Form submitted!');
}}
className="d-flex flex-wrap"
>
<Button variant="primary" type="submit" className="me-4 mb-4">
Submit Button
</Button>
<Button variant="primary" type="reset" className="me-4 mb-4">
Reset Button
</Button>
<Button variant="primary" type="button" onClick={handleClick} className="mb-4">
Regular Button
</Button>
</form>
</section>
{/* Loading State Simulation */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Async Actions</h2>
<h6 className="eyebrow mb-3">Loading States</h6>
</div>
<p className="mb-4 text-muted">
Example of handling async actions. The button is disabled during the async operation.
</p>
<Button variant="primary" disabled={isLoading} onClick={handleAsyncClick}>
{isLoading ? 'Loading...' : 'Async Action'}
</Button>
{clickCount > 0 && (
<p className="mt-4 text-muted">Async action completed {clickCount} time{clickCount !== 1 ? 's' : ''}</p>
)}
</section>
{/* Responsive Behavior */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Responsive Behavior</h2>
<h6 className="eyebrow mb-3">Breakpoint Adjustments</h6>
</div>
<p className="mb-4 text-muted">
Button padding adjusts automatically across breakpoints. Resize your browser window to see the changes:
</p>
<ul className="mb-4">
<li>
<strong>Desktop (1024px):</strong> Padding: 8px 19px 8px 20px, Gap: 16px
</li>
<li>
<strong>Tablet/Mobile (1023px):</strong> Padding: 8px 15px 8px 16px, Gap: 16px
</li>
<li>
<strong>Hover/Focus:</strong> Gap increases (22px desktop, 21px mobile) with adjusted padding to maintain
button width
</li>
</ul>
<div className="d-flex flex-wrap">
<Button variant="primary" onClick={handleClick} className="me-4 mb-4">
Responsive Button
</Button>
<Button variant="primary" onClick={handleClick} className="mb-4">
Long Button Label Example
</Button>
</div>
</section>
{/* Accessibility */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Accessibility Features</h2>
<h6 className="eyebrow mb-3">WCAG Compliance</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Keyboard Navigation</h5>
<ul>
<li>Tab to focus buttons</li>
<li>Enter or Space to activate</li>
<li>Focus indicator: 2px black border</li>
<li>Disabled buttons are not focusable</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Screen Reader Support</h5>
<ul>
<li>Button labels are announced</li>
<li>Disabled state communicated via aria-disabled</li>
<li>Icons are hidden from screen readers (aria-hidden)</li>
<li>Semantic button element used</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">Color Contrast</h5>
<ul>
<li>
<strong>Enabled:</strong> Black text (#141414) on Green 300 (#21E46B) = 9.06:1 (AAA)
</li>
<li>
<strong>Hover:</strong> Black text (#141414) on Green 200 (#70EE97) = 10.23:1 (AAA)
</li>
<li>
<strong>Disabled:</strong> Gray 500 (#838386) on Gray 200 (#E0E0E1) = 2.12:1 (acceptable for disabled
state)
</li>
</ul>
</div>
</section>
{/* Code Examples */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Code Examples</h2>
<h6 className="eyebrow mb-3">Implementation</h6>
</div>
<div className="p-6-sm p-10-until-sm br-8" style={{ backgroundColor: '#1e1e1e', color: '#d4d4d4' }}>
<pre style={{ margin: 0, overflow: 'auto' }}>
<code>{`import { Button } from 'shared/components/Button';
// Basic usage
<Button variant="primary" onClick={handleClick}>
Get Started
</Button>
// Disabled state
<Button variant="primary" disabled>
Submit
</Button>
// Without icon
<Button variant="primary" showIcon={false}>
Continue
</Button>
// Form integration
<Button variant="primary" type="submit">
Submit Form
</Button>`}</code>
</pre>
</div>
</section>
{/* Design Specifications */}
<section className="container-new py-26">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8">Design Specifications</h2>
<h6 className="eyebrow mb-3">Visual Details</h6>
</div>
<PageGrid>
<PageGridRow>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Typography</h5>
<ul>
<li>Font: Booton, sans-serif</li>
<li>Size: 16px</li>
<li>Weight: 400</li>
<li>Line Height: 23.2px</li>
<li>Letter Spacing: 0px</li>
</ul>
</PageGridCol>
<PageGridCol span={{ base: 4, lg: 6 }}>
<h5 className="mb-4">Spacing & Layout</h5>
<ul>
<li>Border Radius: 100px (fully rounded)</li>
<li>Icon Size: 15px × 14px</li>
<li>Icon Gap: 16px (default), 22px (hover/focus desktop), 21px (hover/focus mobile)</li>
<li>Min Height: 40px (touch target)</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
<div className="mt-10">
<h5 className="mb-4">State Colors</h5>
<table className="table" style={{ width: '100%' }}>
<thead>
<tr>
<th>State</th>
<th>Text Color</th>
<th>Background Color</th>
<th>Border</th>
</tr>
</thead>
<tbody>
<tr>
<td>Enabled</td>
<td>#141414 (Neutral Black)</td>
<td>#21E46B (Green 300)</td>
<td>None</td>
</tr>
<tr>
<td>Hover</td>
<td>#141414 (Neutral Black)</td>
<td>#70EE97 (Green 200)</td>
<td>None</td>
</tr>
<tr>
<td>Focus</td>
<td>#141414 (Neutral Black)</td>
<td>#70EE97 (Green 200)</td>
<td>2px solid #141414</td>
</tr>
<tr>
<td>Active</td>
<td>#141414 (Neutral Black)</td>
<td>#21E46B (Green 300)</td>
<td>None</td>
</tr>
<tr>
<td>Disabled</td>
<td>#838386 (Gray 500)</td>
<td>#E0E0E1 (Gray 200)</td>
<td>None</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
);
}

View File

@@ -1,494 +0,0 @@
# Secondary Button Logic
> **Design Source:** [Figma - Button Component](https://www.figma.com/design/MZOPJQOw9oWC6NDxLRH4ws/Button?node-id=5226-8948&m=dev) (Second Column - Secondary)
> **Target Component:** `shared/components/Button/Button.tsx` & `Button.scss`
---
## Overview
The **Secondary Button** is an outline-style button used for secondary actions. It features a transparent background with a colored stroke/border, providing visual hierarchy below the Primary button while maintaining brand consistency.
**Key Characteristics:**
- Outline/ghost style (transparent background in default states)
- Green stroke/border in enabled state
- Filled background appears on hover/focus states
- **Shares arrow icon & animation with Primary button** (no duplicate code)
---
## Shared Code Architecture
The Secondary button **inherits** all arrow icon logic from the base `.bds-btn` class. This ensures:
- ✅ No duplicate arrow animation code
- ✅ Consistent hover behavior across variants
- ✅ Single source of truth for icon styling
### Already Shared in Base `.bds-btn` Class
```scss
// These styles are already in the base class and apply to ALL variants:
.bds-btn {
// Icon element (SVG container) - SHARED
&__icon {
width: 15px;
height: 14px;
flex-shrink: 0;
transition: opacity $bds-btn-transition-duration $bds-btn-transition-timing;
color: currentColor; // Inherits text color from parent
overflow: visible;
}
// Arrow horizontal line - SHARED
&__icon-line {
transform-box: fill-box;
transform-origin: right center;
transform: scaleX(1);
transition: transform $bds-btn-transition-duration $bds-btn-transition-timing;
}
// Arrow chevron - SHARED
&__icon-chevron {
transition: transform $bds-btn-transition-duration $bds-btn-transition-timing;
}
// Hover/Focus animation - SHARED (applies to ALL variants)
&:hover:not(:disabled):not(.bds-btn--disabled),
&:focus-visible:not(:disabled):not(.bds-btn--disabled) {
.bds-btn__icon-line {
transform: scaleX(0); // Line shrinks from left-to-right
}
}
}
```
### What Secondary Button Needs to Define
The `.bds-btn--secondary` class only needs to define:
1. **Colors** (text, background, border) - different from Primary
2. **Padding & Gap** values per state - adjusted for border width
3. **Border** styling (Primary has no border, Secondary has 2px border)
The icon color automatically updates via `currentColor` inheritance from the text color
---
## Visual Specifications
### Button Structure
```
┌─────────────────────────────────────────────┐
│ [Label Text] [gap] [Icon] │
└─────────────────────────────────────────────┘
↑ ↑
.bds-btn__label .bds-btn__icon
```
- **Border Radius:** `100px` (pill shape)
- **Border Width:** `2px` solid
- **Max Height:** `40px`
- **Typography:** Label R token (Booton, 16px, 400 weight, 23.2px line-height)
---
## State Specifications
### 1. Enabled State (Default)
| Property | Value | SCSS Variable |
|----------|-------|---------------|
| **Text Color** | Green 400 `#0DAA3E` | `$green-400` |
| **Background** | Transparent | `transparent` |
| **Border Color** | Green 400 `#0DAA3E` | `$green-400` |
| **Border Width** | `2px` | - |
> **Icon Color:** Inherits text color via `currentColor` (no separate definition needed)
**Desktop Padding & Gap:**
| Property | With Icon | Without Icon |
|----------|-----------|--------------|
| Padding | `8px 19px 8px 20px` | `8px 20px` |
| Gap | `16px` | N/A |
**Tablet/Mobile Padding & Gap (≤1023px):**
| Property | With Icon | Without Icon |
|----------|-----------|--------------|
| Padding | `8px 15px 8px 16px` | `8px 16px` |
| Gap | `16px` | N/A |
---
### 2. Hover State
| Property | Value | SCSS Variable |
|----------|-------|---------------|
| **Text Color** | Green 500 `#078139` | `$green-500` |
| **Background** | Green 100 `#EAFCF1` | `$green-100` |
| **Border Color** | Green 500 `#078139` | `$green-500` |
| **Border Width** | `2px` | - |
> **Icon Color:** Inherits text color via `currentColor` (automatically becomes Green 500)
**Desktop Padding & Gap:**
| Property | With Icon | Without Icon |
|----------|-----------|--------------|
| Padding | `8px 13px 8px 20px` | `8px 20px` |
| Gap | `22px` | N/A |
**Tablet/Mobile Padding & Gap (≤1023px):**
| Property | With Icon | Without Icon |
|----------|-----------|--------------|
| Padding | `8px 10px 8px 16px` | `8px 16px` |
| Gap | `21px` | N/A |
> **Arrow Animation:** Handled by base `.bds-btn` class - line shrinks from left-to-right (shared behavior)
---
### 3. Focus State (Keyboard Navigation)
| Property | Value | SCSS Variable |
|----------|-------|---------------|
| **Text Color** | Green 500 `#078139` | `$green-500` |
| **Background** | Green 100 `#EAFCF1` | `$green-100` |
| **Border Color** | Green 500 `#078139` | `$green-500` |
| **Border Width** | `2px` | - |
| **Focus Ring** | Black `#141414`, 2px | `$bds-btn-primary-focus-border` |
> **Icon Color:** Inherits text color via `currentColor` (automatically becomes Green 500)
The focus state shows a **double border** effect:
1. Inner border: Green 500 (the button's stroke)
2. Outer border: Black (focus indicator ring)
**Desktop Padding & Gap:**
| Property | With Icon | Without Icon |
|----------|-----------|--------------|
| Padding | `6px 11px 6px 18px` | `6px 17px 6px 18px` |
| Gap | `22px` | N/A |
> **Note:** Padding is reduced by 2px on all sides to compensate for the additional focus ring border.
**Tablet/Mobile Padding & Gap (≤1023px):**
| Property | With Icon | Without Icon |
|----------|-----------|--------------|
| Padding | `6px 8px 6px 14px` | `6px 13px 6px 14px` |
| Gap | `21px` | N/A |
---
### 4. Active State (Being Pressed)
| Property | Value | SCSS Variable |
|----------|-------|---------------|
| **Text Color** | Green 400 `#0DAA3E` | `$green-400` |
| **Background** | Transparent | `transparent` |
| **Border Color** | Green 400 `#0DAA3E` | `$green-400` |
| **Border Width** | `2px` | - |
| **Transform** | `scale(0.98)` | - |
> **Icon Color:** Inherits text color via `currentColor` (returns to Green 400)
**Padding & Gap:** Returns to enabled state values
- Desktop: `8px 19px 8px 20px`, gap `16px`
- Tablet/Mobile: `8px 15px 8px 16px`, gap `16px`
---
### 5. Disabled State
| Property | Value | SCSS Variable |
|----------|-------|---------------|
| **Text Color** | Neutral 400 `#8A919A` | `$gray-400` ¹ |
| **Background** | Transparent | `transparent` |
| **Border Color** | Neutral 400 `#8A919A` | `$gray-400` ¹ |
| **Border Width** | `2px` | - |
| **Cursor** | `not-allowed` | - |
| **Pointer Events** | `none` | - |
| **Icon** | Hidden (removed) | - |
> ¹ **Note:** The Figma design uses `#8A919A` for Neutral 400. The existing `_colors.scss` has `$gray-400: #A2A2A4`. Either use the existing variable for consistency or add a new token. Recommend using existing `$gray-400` for codebase consistency.
**Padding & Gap:** Same as enabled state
- Desktop: `8px 19px 8px 20px`, gap `16px`
- Tablet/Mobile: `8px 15px 8px 16px`, gap `16px`
---
## Color Token Mapping
### Required Colors from `_colors.scss`
```scss
// Already available in styles/_colors.scss
$green-100: #EAFCF1; // Hover/Focus background
$green-400: #0DAA3E; // Enabled state (text, border, icon)
$green-500: #078139; // Hover/Focus state (text, border, icon)
$gray-400: #A2A2A4; // Disabled state (text, border) - closest match
```
### Recommended SCSS Variables for Secondary Button
```scss
// Colors - Secondary Button
$bds-btn-secondary-text: $green-400; // #0DAA3E - Enabled
$bds-btn-secondary-text-hover: $green-500; // #078139 - Hover/Focus
$bds-btn-secondary-border: $green-400; // #0DAA3E - Enabled
$bds-btn-secondary-border-hover: $green-500; // #078139 - Hover/Focus
$bds-btn-secondary-bg-hover: $green-100; // #EAFCF1 - Hover/Focus fill
$bds-btn-secondary-disabled-text: $gray-400; // Disabled text & border
$bds-btn-secondary-disabled-border: $gray-400; // Disabled border
```
---
## Implementation Structure
### CSS Class Names (BEM)
```
// SHARED (already exist in base .bds-btn - DO NOT DUPLICATE)
.bds-btn // Base button layout, typography, transitions
.bds-btn__label // Label element
.bds-btn__icon // Icon container (uses currentColor)
.bds-btn__icon-line // Arrow line + hover animation
.bds-btn__icon-chevron // Arrow chevron
.bds-btn--disabled // Disabled state modifier
.bds-btn--no-icon // No icon modifier
// VARIANT-SPECIFIC (add for secondary)
.bds-btn--secondary // Colors, borders, padding/gap values only
```
### Component Props
The existing `ButtonProps` interface supports the secondary variant:
```tsx
interface ButtonProps {
variant?: 'primary' | 'secondary'; // Add 'secondary' to union type
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
type?: 'button' | 'submit' | 'reset';
className?: string;
showIcon?: boolean;
}
```
---
## SCSS Implementation Guide
> ⚠️ **Important:** Do NOT add any icon/arrow styles to `.bds-btn--secondary`. The icon inherits its color via `currentColor` and the hover animation is already handled in the base `.bds-btn` class.
### Design Tokens to Add
```scss
// =============================================================================
// Design Tokens - Secondary Button
// =============================================================================
// Colors - Secondary Button
$bds-btn-secondary-text: $green-400; // #0DAA3E
$bds-btn-secondary-text-hover: $green-500; // #078139
$bds-btn-secondary-bg: transparent;
$bds-btn-secondary-bg-hover: $green-100; // #EAFCF1
$bds-btn-secondary-border: $green-400; // #0DAA3E
$bds-btn-secondary-border-hover: $green-500; // #078139
$bds-btn-secondary-disabled-text: $gray-400; // Disabled state
$bds-btn-secondary-disabled-border: $gray-400; // Disabled border
```
### State Styles Structure
```scss
// =============================================================================
// Secondary Variant
// =============================================================================
// NOTE: Arrow icon animation is inherited from base .bds-btn class.
// The icon color automatically follows text color via currentColor.
// Only define: colors, backgrounds, borders, padding, and gap.
.bds-btn--secondary {
// Default/Enabled state
color: $bds-btn-secondary-text;
background-color: $bds-btn-secondary-bg;
border: 2px solid $bds-btn-secondary-border;
// Desktop padding and gap (≥1024px)
padding: 6px 17px 6px 18px; // Compensate for 2px border
gap: 16px;
// No icon - symmetric padding
&.bds-btn--no-icon {
padding: 6px 18px;
}
// ---------------------------------------------------------------------------
// Hover State
// ---------------------------------------------------------------------------
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: $bds-btn-secondary-text-hover;
background-color: $bds-btn-secondary-bg-hover;
border-color: $bds-btn-secondary-border-hover;
padding: 6px 11px 6px 18px;
gap: 22px;
}
&:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: $bds-btn-secondary-text-hover;
background-color: $bds-btn-secondary-bg-hover;
border-color: $bds-btn-secondary-border-hover;
}
// ---------------------------------------------------------------------------
// Focus State (keyboard navigation)
// ---------------------------------------------------------------------------
&:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: $bds-btn-secondary-text-hover;
background-color: $bds-btn-secondary-bg-hover;
border-color: $bds-btn-secondary-border-hover;
outline: $bds-btn-focus-border-width solid $bds-btn-primary-focus-border;
outline-offset: 0;
padding: 6px 11px 6px 18px;
gap: 22px;
}
&:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: $bds-btn-secondary-text-hover;
background-color: $bds-btn-secondary-bg-hover;
border-color: $bds-btn-secondary-border-hover;
outline: $bds-btn-focus-border-width solid $bds-btn-primary-focus-border;
outline-offset: 0;
padding: 6px 17px 6px 18px;
}
// ---------------------------------------------------------------------------
// Active State (being pressed)
// ---------------------------------------------------------------------------
&:active:not(:disabled):not(.bds-btn--disabled) {
color: $bds-btn-secondary-text;
background-color: $bds-btn-secondary-bg;
border-color: $bds-btn-secondary-border;
transform: scale(0.98);
padding: 6px 17px 6px 18px;
gap: 16px;
}
// ---------------------------------------------------------------------------
// Disabled State
// Note: Icon is hidden via component logic when disabled
// ---------------------------------------------------------------------------
&:disabled,
&.bds-btn--disabled {
color: $bds-btn-secondary-disabled-text;
background-color: transparent;
border-color: $bds-btn-secondary-disabled-border;
cursor: not-allowed;
pointer-events: none;
}
// ---------------------------------------------------------------------------
// Tablet & Mobile Responsive Styles (≤1023px)
// ---------------------------------------------------------------------------
@media (max-width: 1023px) {
padding: 6px 13px 6px 14px;
gap: 16px;
&.bds-btn--no-icon {
padding: 6px 14px;
}
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
padding: 6px 8px 6px 14px;
gap: 21px;
}
&:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
// Background and border color changes applied, padding stable
}
&:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
padding: 6px 8px 6px 14px;
gap: 21px;
}
&:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
padding: 6px 13px 6px 14px;
}
&:active:not(:disabled):not(.bds-btn--disabled) {
padding: 6px 13px 6px 14px;
gap: 16px;
}
}
}
```
---
## Key Differences from Primary Button
| Aspect | Primary | Secondary |
|--------|---------|-----------|
| **Background (Enabled)** | Green 300 `#21E46B` | Transparent |
| **Background (Hover/Focus)** | Green 200 `#70EE97` | Green 100 `#EAFCF1` |
| **Border (Enabled)** | None | 2px Green 400 `#0DAA3E` |
| **Border (Hover/Focus)** | None (only focus ring) | 2px Green 500 `#078139` |
| **Text Color (Enabled)** | Neutral Black `#141414` | Green 400 `#0DAA3E` |
| **Text Color (Hover/Focus)** | Neutral Black `#141414` | Green 500 `#078139` |
| **Disabled Background** | Gray 200 `#E0E0E1` | Transparent |
| **Disabled Border** | None | 2px Gray 400 |
| **Focus Ring** | 2px Black border | 2px Black outline (additional) |
| **Arrow Icon** | ✅ Shared (base class) | ✅ Shared (base class) |
| **Arrow Animation** | ✅ Shared (base class) | ✅ Shared (base class) |
---
## Accessibility Notes
1. **Color Contrast:** Green 400/500 on white background meets WCAG AA requirements
2. **Focus Indicator:** Black outline provides clear keyboard navigation visibility
3. **Disabled State:** Reduced opacity icon + muted colors clearly indicate non-interactive state
4. **Icon:** `aria-hidden="true"` on arrow icon (decorative)
5. **Button:** `aria-disabled` attribute mirrors disabled prop
---
## Usage Example
```tsx
// Secondary button with icon (default)
<Button variant="secondary" onClick={handleClick}>
Learn More
</Button>
// Secondary button without icon
<Button variant="secondary" showIcon={false} onClick={handleClick}>
Cancel
</Button>
// Disabled secondary button
<Button variant="secondary" disabled>
Unavailable
</Button>
```
---
## Checklist for Implementation
- [ ] Add `'secondary'` to variant union type in `ButtonProps`
- [ ] Add Secondary button SCSS variables to design tokens section
- [ ] Implement `.bds-btn--secondary` styles (colors, borders, padding/gap only)
- [ ] Add responsive styles for tablet/mobile breakpoint
- [ ] **DO NOT duplicate arrow icon or animation styles** (inherited from base)
- [ ] Verify arrow icon color changes automatically via `currentColor`
- [ ] Test hover, focus, active, disabled states
- [ ] Verify arrow icon animation works correctly (inherited behavior)
- [ ] Test with and without icon (`showIcon` prop)
- [ ] Verify color contrast meets WCAG requirements
- [ ] Test keyboard navigation (Tab + Enter/Space)

View File

@@ -0,0 +1,459 @@
# Button Component Documentation
## Overview
The Button component is a scalable, accessible button implementation following the XRPL Brand Design System (BDS). It supports three visual variants (Primary, Secondary, Tertiary) and two color themes (Green, Black), with comprehensive state management and smooth animations.
## Features
- **Three Variants**: Primary (solid), Secondary (outline), Tertiary (text-only)
- **Two Color Themes**: Green (default) and Black
- **Animated Arrow Icon**: Optional icon with smooth hover animations
- **Full State Support**: Enabled, Hover, Focus, Active, and Disabled states
- **Responsive Design**: Adaptive padding and spacing across breakpoints
- **Accessibility**: WCAG compliant with keyboard navigation and screen reader support
- **Smooth Animations**: 150ms transitions with custom bezier timing
## Props API
```typescript
interface ButtonProps {
/** Button variant - determines visual style */
variant?: 'primary' | 'secondary' | 'tertiary';
/** Color theme - green (default) or black */
color?: 'green' | 'black';
/** Button content/label */
children: React.ReactNode;
/** Click handler */
onClick?: () => void;
/** Disabled state */
disabled?: boolean;
/** Button type attribute */
type?: 'button' | 'submit' | 'reset';
/** Additional CSS classes */
className?: string;
/** Whether to show the arrow icon */
showIcon?: boolean;
}
```
### Default Values
- `variant`: `'primary'`
- `color`: `'green'`
- `disabled`: `false`
- `type`: `'button'`
- `className`: `''`
- `showIcon`: `true`
## Variants
### Primary Button
The Primary button is used for the main call-to-action on a page. It features a solid background that fills from bottom-to-top on hover.
**Visual Characteristics:**
- Solid background (Green 300 / Black)
- High visual emphasis
- Background color transitions on hover
- Black text on green background, white text on black background
**Usage:**
```tsx
<Button variant="primary" onClick={handleClick}>
Get Started
</Button>
```
### Secondary Button
The Secondary button is used for supporting actions. It features an outline style with a transparent background that fills on hover.
**Visual Characteristics:**
- Transparent background with 2px border
- Medium visual emphasis
- Background fills from bottom-to-top on hover
- Green/Black text and border
**Usage:**
```tsx
<Button variant="secondary" onClick={handleClick}>
Learn More
</Button>
```
### Tertiary Button
The Tertiary button is used for low-emphasis or contextual actions. It appears as text-only with optional underline on hover.
**Visual Characteristics:**
- Text-only, no background or border
- Lowest visual emphasis
- Underline appears on hover/focus
- Different typography (Body R token vs Label R)
**Usage:**
```tsx
<Button variant="tertiary" onClick={handleClick}>
View Details
</Button>
```
## Color Themes
### Green Theme (Default)
The green theme uses the XRPL brand green colors:
- **Primary**: Green 300 background (#21E46B), Green 200 hover (#70EE97)
- **Secondary**: Green 400 text/border (#0DAA3E), Green 500 hover (#078139)
- **Tertiary**: Green 400 text (#0DAA3E), Green 500 hover (#078139)
### Black Theme
The black theme provides an alternative color scheme:
- **Primary**: Black background (#141414), 80% black hover
- **Secondary**: Black text/border (#141414), 15% black hover fill
- **Tertiary**: Black text (#141414)
**Usage:**
```tsx
<Button variant="primary" color="black" onClick={handleClick}>
Dark Button
</Button>
```
## States
### Enabled State
The default interactive state of the button. All variants display their base styling.
### Hover State
Triggered when the user hovers over the button with a mouse:
- **Primary/Secondary**: Background fills from bottom-to-top
- **Tertiary**: Underline appears, text color darkens
- **All Variants**: Arrow icon line shrinks, gap increases (with icon)
### Focus State
Triggered when the button receives keyboard focus (Tab key):
- Similar visual changes to hover state
- Additional focus outline (2px border/outline)
- Ensures keyboard accessibility
### Active State
Triggered when the button is being pressed:
- Returns to default padding/gap
- Background resets (for Primary/Secondary)
- Maintains visual feedback during press
### Disabled State
When `disabled={true}`:
- Icon is automatically hidden
- Gray text and background (Primary) or border (Secondary)
- Cursor changes to `not-allowed`
- `pointer-events: none` prevents interaction
- `aria-disabled` attribute set for screen readers
**Usage:**
```tsx
<Button variant="primary" disabled>
Unavailable
</Button>
```
## How It Works
### Component Structure
The Button component uses BEM (Block Element Modifier) naming convention with the `bds` namespace:
- `.bds-btn` - Base button class
- `.bds-btn--primary` - Primary variant modifier
- `.bds-btn--secondary` - Secondary variant modifier
- `.bds-btn--tertiary` - Tertiary variant modifier
- `.bds-btn--green` - Green color theme (default)
- `.bds-btn--black` - Black color theme
- `.bds-btn--disabled` - Disabled state modifier
- `.bds-btn--no-icon` - No icon modifier
- `.bds-btn__label` - Label element
- `.bds-btn__icon` - Icon container
- `.bds-btn__icon-line` - Arrow horizontal line
- `.bds-btn__icon-chevron` - Arrow chevron
### Background Animation
Primary and Secondary variants use a shared animation pattern:
1. **Pseudo-element (`::before`)**: Creates the hover background fill
2. **Transform Origin**: Set to `bottom center` for bottom-to-top fill
3. **Initial State**: `scaleY(0)` - background hidden
4. **Hover/Focus**: `scaleY(1)` - background fills from bottom
5. **Active**: `scaleY(0)` - background resets during press
This creates a smooth, directional fill animation that feels natural and responsive.
### Arrow Icon Animation
The arrow icon consists of two parts:
1. **Horizontal Line**: Shrinks from right to left (`scaleX(0)`) on hover/focus
2. **Chevron**: Stays visible, shifts right via increased gap
The gap between label and icon increases on hover/focus:
- **Default**: 16px (desktop), 16px (mobile)
- **Hover/Focus**: 22px (desktop), 21px (mobile)
This creates the illusion of the arrow "moving forward" as the line disappears.
### Padding Adjustments
On hover/focus, padding adjusts to accommodate the increased gap:
- **Primary**: `8px 19px 8px 20px``8px 13px 8px 20px` (desktop)
- **Secondary**: `6px 17px 6px 18px``6px 11px 6px 18px` (desktop)
- **Tertiary**: `8px 20px``8px 14px 8px 20px` (desktop)
These adjustments maintain visual balance while allowing the icon animation to work smoothly.
### Responsive Behavior
The component adapts to screen size at the `1023px` breakpoint:
**Desktop (≥1024px):**
- Larger padding values
- 22px gap on hover/focus
**Tablet/Mobile (≤1023px):**
- Reduced padding values
- 21px gap on hover/focus
All transitions remain smooth across breakpoints.
## Typography
### Primary & Secondary Variants
- **Font**: Booton, sans-serif
- **Size**: 16px (Label R token)
- **Weight**: 400
- **Line Height**: 23.2px
- **Letter Spacing**: 0px
### Tertiary Variant
- **Font**: Booton, sans-serif
- **Size**: 18px (Body R token)
- **Weight**: 400
- **Line Height**: 26.1px
- **Letter Spacing**: -0.5px
## Spacing & Layout
- **Border Radius**: 100px (fully rounded)
- **Max Height**: 40px
- **Icon Size**: 15px × 14px
- **Transitions**: 150ms with `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
## Usage Examples
### Basic Usage
```tsx
import { Button } from 'shared/components/Button';
// Primary button (default)
<Button onClick={handleClick}>
Get Started
</Button>
// Secondary button
<Button variant="secondary" onClick={handleClick}>
Learn More
</Button>
// Tertiary button
<Button variant="tertiary" onClick={handleClick}>
View Details
</Button>
```
### Form Integration
```tsx
<form onSubmit={handleSubmit}>
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="tertiary" type="reset">
Reset
</Button>
<Button variant="tertiary" type="button" onClick={handleCancel}>
Cancel
</Button>
</form>
```
### Without Icon
```tsx
<Button variant="primary" showIcon={false} onClick={handleClick}>
No Arrow
</Button>
```
### Disabled State
```tsx
<Button variant="primary" disabled>
Unavailable
</Button>
```
### Color Themes
```tsx
{/* Green theme (default) */}
<Button variant="primary" color="green" onClick={handleClick}>
Green Button
</Button>
{/* Black theme */}
<Button variant="primary" color="black" onClick={handleClick}>
Black Button
</Button>
```
### Visual Hierarchy
```tsx
{/* Use variants to create clear visual hierarchy */}
<Button variant="primary" onClick={handlePrimaryAction}>
Main Action
</Button>
<Button variant="secondary" onClick={handleSecondaryAction}>
Secondary Action
</Button>
<Button variant="tertiary" onClick={handleTertiaryAction}>
Tertiary Action
</Button>
```
## Accessibility
### Keyboard Navigation
- **Tab**: Focus next button
- **Shift+Tab**: Focus previous button
- **Enter/Space**: Activate button
- **Focus Indicator**: Visible outline/border (2px)
- **Disabled buttons**: Not focusable
### Screen Reader Support
- Semantic `<button>` element
- Button label announced
- `aria-disabled` attribute for disabled state
- Icon marked with `aria-hidden="true"`
### Color Contrast
All variants meet WCAG AA standards:
- **Primary**: Black on Green 300 = sufficient contrast
- **Secondary/Tertiary**: Green 400/500 on White = 4.52:1 / 5.12:1
- **Disabled**: Gray 400/500 indicates non-interactive state
### Focus Management
- Focus outline appears on keyboard navigation (`:focus-visible`)
- Focus styles match hover styles for consistency
- Square corners on Tertiary focus outline for better visibility
## Design Tokens
The component uses design tokens from the XRPL Brand Design System:
### Colors
- `$green-100` through `$green-500`
- `$gray-200`, `$gray-400`, `$gray-500`
- `$white`
- Neutral black (`#141414`)
### Spacing
- Border radius: `100px`
- Focus border width: `2px`
- Responsive breakpoint: `1023px`
### Motion
- Transition duration: `150ms`
- Timing function: `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
## Best Practices
1. **Use Primary for main actions**: Reserve primary buttons for the most important action on a page
2. **Use Secondary for supporting actions**: Use secondary buttons for actions that support the primary action
3. **Use Tertiary for low-emphasis actions**: Use tertiary buttons for cancel, skip, or less important actions
4. **Maintain visual hierarchy**: Don't use multiple primary buttons on the same page
5. **Provide clear labels**: Button text should clearly indicate the action
6. **Handle disabled states**: Always provide feedback when actions are unavailable
7. **Test keyboard navigation**: Ensure all buttons are accessible via keyboard
8. **Consider context**: Choose color theme based on background and design context
## Implementation Details
### Class Name Generation
The component builds class names dynamically:
```typescript
const classNames = [
'bds-btn',
`bds-btn--${variant}`,
`bds-btn--${color}`,
disabled ? 'bds-btn--disabled' : '',
!shouldShowIcon ? 'bds-btn--no-icon' : '',
className,
]
.filter(Boolean)
.join(' ');
```
### Icon Visibility Logic
The icon is automatically hidden when:
- `showIcon={false}` is passed
- `disabled={true}` is set
This ensures disabled buttons don't show interactive elements.
### State Management
The component manages states through CSS classes and props:
- **Disabled**: Controlled via `disabled` prop and `aria-disabled` attribute
- **Hover/Focus**: Handled by CSS `:hover` and `:focus-visible` pseudo-classes
- **Active**: Handled by CSS `:active` pseudo-class
## Browser Support
The component uses modern CSS features:
- CSS Grid/Flexbox (widely supported)
- `:focus-visible` (supported in modern browsers)
- CSS transforms and transitions (widely supported)
- CSS custom properties (supported in modern browsers)
For older browser support, consider polyfills or fallbacks as needed.
## Related Components
- See showcase pages for interactive examples:
- `about/button-showcase-tertiary.page.tsx`
- Other variant showcase pages
## File Structure
```
shared/components/Button/
├── Button.tsx # Component implementation
├── Button.scss # Component styles
├── Button.md # This documentation
└── index.ts # Exports
```

View File

@@ -7,7 +7,7 @@
// .bds-btn--secondary - Secondary variant (outline style)
// .bds-btn--green - Green color theme (default)
// .bds-btn--black - Black color theme
// .bds-btn--tertiary - (Future) Tertiary variant
// .bds-btn--tertiary - Tertiary variant (text-only style)
// .bds-btn__label - Label element
// .bds-btn__icon - Icon element (inherits color via currentColor)
// .bds-btn--disabled - Disabled state modifier
@@ -49,11 +49,22 @@ $bds-btn-secondary-border-hover: $green-500; // #078139 - Hover/Focus
$bds-btn-secondary-disabled-text: $gray-400; // Disabled text
$bds-btn-secondary-disabled-border: $gray-400; // Disabled border
// Colors - Tertiary Button
$bds-btn-tertiary-text: $green-400; // #0DAA3E - Enabled
$bds-btn-tertiary-text-hover: $green-500; // #078139 - Hover/Focus/Active
$bds-btn-tertiary-bg: transparent;
$bds-btn-tertiary-focus-outline: $bds-btn-neutral-black; // #141414 - Focus outline (black)
$bds-btn-tertiary-disabled-text: $gray-400; // Disabled text
// Colors - Black Secondary Button
$bds-btn-secondary-black-text: $bds-btn-neutral-black;
$bds-btn-secondary-black-bg-hover: $bds-btn-neutral-black-15;
$bds-btn-secondary-black-border: $bds-btn-neutral-black;
// Colors - Black Tertiary Button
$bds-btn-tertiary-black-text: $bds-btn-neutral-black;
$bds-btn-tertiary-black-focus-outline: $bds-btn-neutral-black;
// Spacing
$bds-btn-border-radius: 100px;
$bds-btn-focus-border-width: 2px;
@@ -439,9 +450,9 @@ $bds-btn-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
// Color Themes
// =============================================================================
// Green theme (.bds-btn--green) is the default - no overrides needed.
// Black theme (.bds-btn--black) overrides colors for both variants.
// Black theme (.bds-btn--black) overrides colors for all variants.
// Black theme - overrides colors for primary and secondary variants
// Black theme - overrides colors for primary, secondary, and tertiary variants
.bds-btn--black {
// ---------------------------------------------------------------------------
// Black Primary Button
@@ -519,13 +530,191 @@ $bds-btn-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
border-color: $bds-btn-secondary-disabled-border;
}
}
// ---------------------------------------------------------------------------
// Black Tertiary Button
// ---------------------------------------------------------------------------
&.bds-btn--tertiary {
color: $bds-btn-tertiary-black-text;
// Hover state - text stays black, underline appears (override base tertiary green hover)
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: $bds-btn-tertiary-black-text !important; // Ensure black color overrides green
text-decoration: underline;
padding: 8px 14px 8px 20px; // Match base tertiary hover padding
gap: 22px; // Match base tertiary hover gap
}
&:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: $bds-btn-tertiary-black-text !important; // Ensure black color overrides green
text-decoration: underline;
}
// Focus state
&:focus-visible:not(:disabled):not(.bds-btn--disabled) {
color: $bds-btn-tertiary-black-text !important; // Ensure black color overrides green
text-decoration: underline;
border-radius: 0; // Square corners for focus outline
outline: 2px solid $bds-btn-tertiary-black-focus-outline;
outline-offset: -2px; // Negative offset brings outline closer to content
// Padding will be inherited from base tertiary focus state
}
// Active state
&:active:not(:disabled):not(.bds-btn--disabled) {
color: $bds-btn-tertiary-black-text !important; // Ensure black color overrides green
text-decoration: underline;
}
// Disabled state - same as green disabled
&:disabled,
&.bds-btn--disabled {
color: $bds-btn-disabled-text;
}
// Responsive hover states - ensure black color on mobile too
@media (max-width: 1023px) {
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: $bds-btn-tertiary-black-text !important;
padding: 8px 11px 8px 16px; // Match base tertiary mobile hover padding
gap: 21px;
}
}
}
}
// =============================================================================
// Future Variants (Placeholder structure for scalability)
// Tertiary Variant
// =============================================================================
// NOTE: Arrow icon animation is inherited from base .bds-btn class.
// The icon color automatically follows text color via currentColor.
// Only define: colors, text decoration, padding, gap, and focus outline.
// .bds-btn--tertiary {
// // Text-only style: green text, no border/fill
// // States: enabled, hover (underline), focus, active, disabled
// }
.bds-btn--tertiary {
// Default/Enabled state
color: $bds-btn-tertiary-text;
background-color: $bds-btn-tertiary-bg;
border: none;
text-decoration: none;
// Typography - Body R token (different from Primary/Secondary)
font-size: 18px;
line-height: 26.1px;
letter-spacing: -0.5px;
// Desktop padding and gap (≥1024px)
padding: 8px 20px;
gap: 16px;
// No icon - symmetric padding
&.bds-btn--no-icon {
padding: 8px 20px;
}
// Disable background animation (no ::before needed for tertiary)
&::before {
display: none;
}
// ---------------------------------------------------------------------------
// Hover State
// ---------------------------------------------------------------------------
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: $bds-btn-tertiary-text-hover;
text-decoration: underline;
padding: 8px 14px 8px 20px;
gap: 22px;
}
&:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: $bds-btn-tertiary-text-hover;
text-decoration: underline;
}
// ---------------------------------------------------------------------------
// Focus State (keyboard navigation)
// ---------------------------------------------------------------------------
&:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: $bds-btn-tertiary-text-hover;
text-decoration: underline;
border-radius: 0; // Square corners for focus outline
outline: 2px solid $bds-btn-tertiary-focus-outline;
outline-offset: -2px; // Negative offset brings outline closer to content
padding: 0 8px 0 9px; // Reduced left padding by 5px (was 14px)
gap: 22px;
}
&:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: $bds-btn-tertiary-text-hover;
text-decoration: underline;
border-radius: 0; // Square corners for focus outline
outline: 2px solid $bds-btn-tertiary-focus-outline;
outline-offset: -2px; // Negative offset brings outline closer to content
padding: 0 9px; // Reduced left padding by 5px (was 14px)
}
// ---------------------------------------------------------------------------
// Active State (being pressed)
// ---------------------------------------------------------------------------
&:active:not(:disabled):not(.bds-btn--disabled) {
color: $bds-btn-tertiary-text;
text-decoration: underline;
padding: 8px 20px;
gap: 16px;
}
// ---------------------------------------------------------------------------
// Disabled State
// Note: Icon is hidden via component logic when disabled
// ---------------------------------------------------------------------------
&:disabled,
&.bds-btn--disabled {
color: $bds-btn-tertiary-disabled-text;
background-color: transparent;
text-decoration: none;
cursor: not-allowed;
pointer-events: none;
// Disable background animation on disabled
&::before {
display: none;
}
}
// ---------------------------------------------------------------------------
// Tablet & Mobile Responsive Styles (≤1023px)
// ---------------------------------------------------------------------------
@media (max-width: 1023px) {
padding: 8px 16px;
gap: 16px;
&.bds-btn--no-icon {
padding: 8px 16px;
}
&:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
padding: 8px 11px 8px 16px;
gap: 21px;
}
&:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
border-radius: 0; // Square corners for focus outline
outline: 2px solid $bds-btn-tertiary-focus-outline;
outline-offset: -2px; // Negative offset brings outline closer to content
padding: 0 5px 0 2px; // Reduced left padding by 5px (was 10px)
gap: 21px;
}
&:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
border-radius: 0; // Square corners for focus outline
outline: 2px solid $bds-btn-tertiary-focus-outline;
outline-offset: -2px; // Negative offset brings outline closer to content
padding: 0 2px; // Reduced left padding by 5px (was 10px)
}
&:active:not(:disabled):not(.bds-btn--disabled) {
padding: 8px 16px;
gap: 16px;
}
}
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
export interface ButtonProps {
/** Button variant - determines visual style */
variant?: 'primary' | 'secondary';
variant?: 'primary' | 'secondary' | 'tertiary';
/** Color theme - green (default) or black */
color?: 'green' | 'black';
/** Button content/label */
@@ -61,11 +61,12 @@ const ArrowIcon: React.FC = () => (
* BDS Button Component
*
* A scalable button component following the XRPL Brand Design System.
* Supports Primary and Secondary variants with green (default) or black color themes.
* Supports Primary, Secondary, and Tertiary variants with green (default) or black color themes.
*
* @example
* <Button variant="primary" onClick={handleClick}>Get Started</Button>
* <Button variant="secondary" onClick={handleClick}>Learn More</Button>
* <Button variant="tertiary" onClick={handleClick}>View Details</Button>
* <Button variant="primary" color="black" onClick={handleClick}>Dark Button</Button>
*/
export const Button: React.FC<ButtonProps> = ({

View File

@@ -11415,6 +11415,131 @@ button[disabled=disabled] {
color: #838386;
border-color: #A2A2A4;
}
.bds-btn--black.bds-btn--tertiary {
color: #141414;
}
.bds-btn--black.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: #141414 !important;
text-decoration: underline;
padding: 8px 14px 8px 20px;
gap: 22px;
}
.bds-btn--black.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: #141414 !important;
text-decoration: underline;
}
.bds-btn--black.bds-btn--tertiary:focus-visible:not(:disabled):not(.bds-btn--disabled) {
color: #141414 !important;
text-decoration: underline;
border-radius: 0;
outline: 2px solid #141414;
outline-offset: -2px;
}
.bds-btn--black.bds-btn--tertiary:active:not(:disabled):not(.bds-btn--disabled) {
color: #141414 !important;
text-decoration: underline;
}
.bds-btn--black.bds-btn--tertiary:disabled, .bds-btn--black.bds-btn--tertiary.bds-btn--disabled {
color: #838386;
}
@media (max-width: 1023px) {
.bds-btn--black.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: #141414 !important;
padding: 8px 11px 8px 16px;
gap: 21px;
}
}
.bds-btn--tertiary {
color: #0DAA3E;
background-color: transparent;
border: none;
text-decoration: none;
font-size: 18px;
line-height: 26.1px;
letter-spacing: -0.5px;
padding: 8px 20px;
gap: 16px;
}
.bds-btn--tertiary.bds-btn--no-icon {
padding: 8px 20px;
}
.bds-btn--tertiary::before {
display: none;
}
.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: #078139;
text-decoration: underline;
padding: 8px 14px 8px 20px;
gap: 22px;
}
.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: #078139;
text-decoration: underline;
}
.bds-btn--tertiary:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
color: #078139;
text-decoration: underline;
border-radius: 0;
outline: 2px solid #141414;
outline-offset: -2px;
padding: 0 8px 0 9px;
gap: 22px;
}
.bds-btn--tertiary:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
color: #078139;
text-decoration: underline;
border-radius: 0;
outline: 2px solid #141414;
outline-offset: -2px;
padding: 0 9px;
}
.bds-btn--tertiary:active:not(:disabled):not(.bds-btn--disabled) {
color: #0DAA3E;
text-decoration: underline;
padding: 8px 20px;
gap: 16px;
}
.bds-btn--tertiary:disabled, .bds-btn--tertiary.bds-btn--disabled {
color: #A2A2A4;
background-color: transparent;
text-decoration: none;
cursor: not-allowed;
pointer-events: none;
}
.bds-btn--tertiary:disabled::before, .bds-btn--tertiary.bds-btn--disabled::before {
display: none;
}
@media (max-width: 1023px) {
.bds-btn--tertiary {
padding: 8px 16px;
gap: 16px;
}
.bds-btn--tertiary.bds-btn--no-icon {
padding: 8px 16px;
}
.bds-btn--tertiary:hover:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
padding: 8px 11px 8px 16px;
gap: 21px;
}
.bds-btn--tertiary:focus-visible:not(:disabled):not(.bds-btn--disabled):not(.bds-btn--no-icon) {
border-radius: 0;
outline: 2px solid #141414;
outline-offset: -2px;
padding: 0 5px 0 2px;
gap: 21px;
}
.bds-btn--tertiary:focus-visible:not(:disabled):not(.bds-btn--disabled).bds-btn--no-icon {
border-radius: 0;
outline: 2px solid #141414;
outline-offset: -2px;
padding: 0 2px;
}
.bds-btn--tertiary:active:not(:disabled):not(.bds-btn--disabled) {
padding: 8px 16px;
gap: 16px;
}
}
/* TABLE STYLING */
article table {