Quick implementation
.secondary-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.625rem 1.25rem;
background-color: var(--card);
color: var(--text);
font-weight: 600;
font-size: 0.95rem;
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: all 0.2s ease;
}
.secondary-button:hover {
background-color: oklch(0.27 0.02 260);
transform: translateY(-2px);
box-shadow: 0 0.5rem 1rem oklch(0 0 0 / 0.15);
}
.secondary-button:active {
transform: translateY(0);
box-shadow: 0 0.25rem 0.5rem oklch(0 0 0 / 0.1);
}
.secondary-button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.secondary-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer specializing in accessible, cohesive button design systems.
Your goal: Create a secondary button component that complements a primary CTA button—less prominent, lower visual weight, and supporting the primary action.
Technical constraints:
1. Secondary buttons should use a muted background (card color or slightly darker)
2. Text color should be readable on dark backgrounds—use full contrast
3. Hover state must be subtle: slight background change + elevation (lift effect)
4. Active state should show reduced elevation with pressed appearance
5. All interactive states must meet WCAG AAA contrast ratios (7:1 for text)
6. Must support disabled state with reduced opacity and no-pointer-events
7. Focus state must use visible outline (2px solid) for keyboard navigation
8. Padding should be consistent with primary button (0.625rem vertical)
Vanilla implementation:
- Create button.secondary-button selector with base styles
- Use :hover, :active, :disabled, :focus-visible pseudo-classes
- Implement elevation change with transform: translateY and box-shadow
- Provide disabled state with opacity and cursor: not-allowed
React implementation:
- Create Button component with variant prop (primary, secondary)
- Pass variant to className for conditional styling
- Support isDisabled prop that applies disabled styles and removes event handlers
- Use forwardRef to expose native button element
- Optional: use Compound Component pattern for Button, Button.Icon, Button.Label
Edge cases to handle:
1. Icon buttons (square) should maintain aspect ratio
2. Loading state (spinner) should temporarily disable button
3. Group of buttons (primary + secondary) should be visually distinct
4. Very small viewports: ensure buttons stay clickable (min 44px height)
5. Dark mode: test contrast ratios in both light and dark themes
Why this matters in 2026
Button design is a solved problem, but only when you have a cohesive system. The secondary button is the workhorse of modern UI—it's used for "Cancel," "Go back," "Learn more," and every action that isn't the primary goal. Without a well-designed secondary button, primary CTAs get lost in the clutter.
In 2026, users expect instant visual feedback from all interactive elements. A secondary button that lifts on hover and sinks on press communicates interactivity through physics metaphors that feel natural and responsive. This small interaction detail differentiates premium UX from amateur work.
The logic
The secondary button uses var(--card) as its background, which is a subtle, neutral tone that pairs well with bright primary buttons. On hover, the background gets slightly lighter (oklch(0.27 0.02 260)) and the button lifts via transform: translateY(-2px), paired with a gentle box-shadow to create the illusion of depth.
The active state removes the lift effect and reduces shadow, simulating a "pressed" button. The focus-visible outline uses the accent color and 2px width for clear keyboard navigation. The :disabled state uses opacity: 0.5 and cursor: not-allowed to indicate non-interactivity. All color transitions use a 0.2s ease curve for snappy but not jarring feedback.
Accessibility & performance
Accessibility: The secondary button must maintain sufficient color contrast on dark backgrounds. Using var(--text) (which is light) on var(--card) (which is dark) ensures WCAG AAA compliance. The focus-visible outline is essential for keyboard users. Always include a disabled state that prevents interaction and communicates unavailability through reduced opacity.
Performance: The transform and box-shadow transitions are GPU-accelerated and cause zero layout recalculations. The 0.2s duration is fast enough to feel responsive without introducing noticeable lag. Avoid animating width, padding, or border-radius, which trigger reflow and paint operations.