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 button
  • border-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