CSS Button Generator: From Presets to Interactive States
CSS Button Generator: From Presets to Interactive States#
Working on an admin dashboard project, the UI designer handed me a pile of button styles: primary, secondary, danger, ghost… Each with different colors, border-radius, shadows. Writing CSS by hand works, but tweaking values means editing multiple places. So I built a button generator — drag sliders, get code.
Core Button Properties#
A complete button style boils down to these:
.btn {
padding: 12px 24px; /* Padding */
font-size: 16px; /* Font size */
color: #ffffff; /* Text color */
background-color: #3b82f6; /* Background */
border: none; /* Border */
border-radius: 6px; /* Border radius */
cursor: pointer; /* Cursor */
transition: all 0.2s ease; /* Transition */
}
But static styles aren’t enough. Buttons need interactive feedback — visual changes on hover and click.
Implementing Interactive States#
1. Hover State#
The most common hover treatment:
.btn:hover {
opacity: 0.9; /* Slight transparency */
transform: translateY(-1px); /* Lift up 1px */
box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.15); /* Deeper shadow */
}
Using translateY(-1px) makes the button “float”, combined with a deeper shadow to simulate lifting.
2. Active State#
Feedback on click:
.btn:active {
transform: translateY(0); /* Return to original */
box-shadow: none; /* Shadow disappears */
}
This gives a “pressed down” feeling when clicked.
3. Dynamic Shadow Generation#
Shadows add depth, but writing them manually is tedious. We can auto-generate shadows based on button color:
function generateShadow(baseColor: string, intensity: number = 0.2): string {
// Parse color to RGB
const r = parseInt(baseColor.slice(1, 3), 16)
const g = parseInt(baseColor.slice(3, 5), 16)
const b = parseInt(baseColor.slice(5, 7), 16)
// Generate shadow with transparency
return `0 4px 6px -1px rgba(${r}, ${g}, ${b}, ${intensity})`
}
// Usage
const shadow = generateShadow('#3b82f6', 0.3)
// Output: 0 4px 6px -1px rgba(59, 130, 246, 0.3)
This creates shadows that harmonize with the button color, instead of plain black.
Designing Preset Styles#
Common button types in projects can be packaged as presets:
const presets = [
{ name: 'Primary', bg: '#3b82f6', color: '#ffffff', border: 'none', radius: 6 },
{ name: 'Success', bg: '#10b981', color: '#ffffff', border: 'none', radius: 6 },
{ name: 'Danger', bg: '#ef4444', color: '#ffffff', border: 'none', radius: 6 },
{ name: 'Warning', bg: '#f59e0b', color: '#ffffff', border: 'none', radius: 6 },
{ name: 'Secondary', bg: 'transparent', color: '#3b82f6', border: '2px solid #3b82f6', radius: 6 },
{ name: 'Ghost', bg: 'transparent', color: '#ffffff', border: '2px solid #ffffff', radius: 6 },
]
When users click a preset, all parameters auto-fill:
function applyPreset(preset: Preset) {
setBgColor(preset.bg)
setTextColor(preset.color)
setBorderWidth(preset.border === 'none' ? 0 : 2)
setBorderColor(preset.border === 'none' ? bgColor : '#3b82f6')
setRadius(preset.radius)
}
Live Preview & Code Generation#
1. Two-Way Binding#
Manage all parameters with React state:
const [bgColor, setBgColor] = useState('#3b82f6')
const [textColor, setTextColor] = useState('#ffffff')
const [borderWidth, setBorderWidth] = useState(0)
const [radius, setRadius] = useState(6)
const [paddingX, setPaddingX] = useState(24)
const [paddingY, setPaddingY] = useState(12)
When any parameter changes, preview and code update simultaneously.
2. CSS Code Template#
Dynamic generation with template strings:
const cssCode = `.btn {
display: inline-block;
padding: ${paddingY}px ${paddingX}px;
font-size: ${fontSize}px;
font-weight: 500;
color: ${textColor};
background-color: ${bgColor};
border: ${borderWidth}px solid ${borderColor};
border-radius: ${radius}px;
cursor: pointer;
transition: all 0.2s ease;${shadow ? `
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);` : ''}
}
.btn:hover {
opacity: 0.9;
transform: translateY(-1px);${shadow ? `
box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.15);` : ''}
}
.btn:active {
transform: translateY(0);
}`
3. One-Click Copy#
Using the Clipboard API:
const handleCopy = async () => {
await navigator.clipboard.writeText(cssCode)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
Edge Cases#
1. Ghost Buttons with Transparent Background#
When background is transparent, shadows need special handling:
.ghost-btn {
background-color: transparent;
border: 2px solid #3b82f6;
box-shadow: none; /* No shadow on transparent background */
}
.ghost-btn:hover {
background-color: rgba(59, 130, 246, 0.1); /* Light fill on hover */
}
2. Border vs Background Color Conflict#
For bordered buttons, border and background colors should coordinate:
// When border width > 0, border color defaults to background color
const borderColor = borderWidth > 0 ? bgColor : '#3b82f6'
3. Extreme Border Radius Values#
border-radius: 0→ Square buttonborder-radius: 9999px→ Pill button
// Quick toggle options
const radiusPresets = [
{ name: 'Square', value: 0 },
{ name: 'Rounded', value: 6 },
{ name: 'Pill', value: 9999 },
]
Real-World Use Cases#
1. Form Submission#
<button className="btn btn-primary" type="submit">
Submit
</button>
2. Dangerous Action Confirmation#
<button className="btn btn-danger" onClick={handleDelete}>
Delete
</button>
3. Secondary Actions#
<button className="btn btn-secondary">
Cancel
</button>
The Result#
Based on these ideas, I built: Button Generator
Features:
- 6 preset styles, one-click apply
- Live preview of hover/click effects
- Custom colors, radius, shadows
- One-click copy CSS code
The implementation isn’t complex, but getting interaction details right takes effort. Hope this helps.
Related: Border Generator | Shadow Generator