ARTICLE

Custom CSS: Button Styles and Effects

Replace the default feedback buttons with your own class and CSS, style each button apart, and add effects that respect reduced motion.

Qaid Team
TL;DR

Set buttonClass and the widget drops its own button look, leaving your class on each button for your css to style. .qaid-btn-up and .qaid-btn-down tell the two apart. Your class also lands on the up/down badge in the message box.

The default buttons are grey circles. To draw your own, give the buttons a class and pass the CSS for it. How CSS gets into the widget at all is covered in Styling the feedback embed; this page is about the buttons.

Replace the default button style

With buttonClass set, each button is built like this:

<div class="qaid-buttons">
  <div class="qaid-tooltip-wrapper">
    <button class="qaid-btn-structural my-btn qaid-btn-up">…</button>
  </div>
  <div class="qaid-tooltip-wrapper">
    <button class="qaid-btn-structural my-btn qaid-btn-down">…</button>
  </div>
</div>

qaid-btn-structural only centres the icon. The size, circle, grey fill and hover of the default button are all gone, so your class has to set them:

new QaidFeedback({
  endpoint: "https://qaid.dev/api/feedback",
  apiKey: "YOUR_PROJECT_KEY",
  buttonClass: "my-btn",
  css: `
    button.my-btn {
      width: var(--qaid-btn-size);
      height: var(--qaid-btn-size);
      border: 2px solid #111827;
      border-radius: 12px;
      background: #fff;
      color: #111827;
    }
    button.my-btn:hover { background: #111827; color: #fff; }
  `,
});

--qaid-btn-size is 36, 48 or 64 pixels, following buttonSize. The built-in icons are 24 pixels whatever the size. An emoji icon grows with it, and so does your own SVG icon when it has no width (thumbs-embed 1.8.0 or later). On the script tag, use data-button-class="my-btn" and a <style> block named by data-css-selector.

Style up and down differently

Class Button
.qaid-btn-up, .qaid-btn-down Thumbs up, thumbs down
.qaid-btn-feedback The one button in single-button mode
.qaid-btn-record The record button, when video is on
.qaid-dismiss-btn The ×. It never gets your class

Write button.my-btn.qaid-btn-up { … } for the thumbs-up alone.

Mind the badge in the message box

The badge at the top of the message box, which flips between up and down, gets your class too: qaid-type-toggle qaid-type-toggle-custom my-btn qaid-btn-up. Your size and colour rules reach it. To split them, use .qaid-buttons .my-btn for the buttons on the page and .qaid-type-toggle.my-btn for the badge. If a custom icon fills the whole badge, give it a size: .qaid-type-toggle svg { width: 1.25rem; height: 1.25rem; }.

Loading the film…

Recolour without a class

To keep the default buttons and change only their colours, skip buttonClass and write rules for button.qaid-btn-up and button.qaid-btn-down in css. Brand color matching lists the tokens that make this a two-line job.

Three effects

Each demo below is live. Click a button to open the message box and see the badge.

Frosted glass

Loading the live demo…
button.glass {
  width: var(--qaid-btn-size);
  height: var(--qaid-btn-size);
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, .3);
  background: rgba(255, 255, 255, .15);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: #fff;
  transition: background .2s, transform .2s;
}
button.glass:hover { background: rgba(255, 255, 255, .3); transform: translateY(-2px); }
button.glass.qaid-btn-up:hover { box-shadow: 0 8px 24px rgba(74, 222, 128, .4); }
button.glass.qaid-btn-down:hover { box-shadow: 0 8px 24px rgba(248, 113, 113, .4); }

It needs something colourful behind it. Over a plain page the blur has nothing to blur.

Pixel press

Loading the live demo…
button.pixel {
  width: var(--qaid-btn-size);
  height: var(--qaid-btn-size);
  border: 3px solid #000;
  border-radius: 0;
  background: #fde047;
  color: #000;
  box-shadow: 4px 4px 0 #000;
  transition: transform .1s, box-shadow .1s;
}
button.pixel.qaid-btn-down { background: #f472b6; }
button.pixel:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 #000; }
button.pixel:active { transform: translate(4px, 4px); box-shadow: 0 0 0 #000; }

It rests on a hard, unblurred shadow. On press, the button moves onto its shadow and the shadow goes to zero.

Pulse ring

Loading the live demo…
button.pulse {
  position: relative;
  width: var(--qaid-btn-size);
  height: var(--qaid-btn-size);
  border: 2px solid #334155;
  border-radius: 50%;
  background: #0f172a;
  color: #e2e8f0;
}
button.pulse.qaid-btn-up:hover { border-color: #4ade80; }
button.pulse.qaid-btn-down:hover { border-color: #f87171; }
button.pulse::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid currentColor;
  opacity: 0;
  pointer-events: none;
}
button.pulse:hover::after { animation: pulse-ring 1s ease-out infinite; }
@keyframes pulse-ring {
  from { transform: scale(1); opacity: .6; }
  to { transform: scale(1.6); opacity: 0; }
}

@keyframes works inside css like any other rule. The hover also changes the border colour, which matters for the next section.

Respect reduced motion

When a visitor’s system asks for reduced motion, the widget’s own sheet sets animation: none !important and transition: none !important on everything inside it, your buttons included. Hover states still apply; they just snap into place. So the pulse ring above never shows for those visitors, but the border still changes colour.

Leave that rule alone rather than beating it with !important of your own. Instead, make sure every state reads without motion: a colour, border or shadow change that carries the meaning on its own.

Back to all articles