17 CSS Range Sliders04 / 17

CSS + JSMIT licensed

Custom Emoji Mood Selector

An interactive feedback slider that transitions between five emoji states from sad to ecstatic, with a pulsing ring animation, smooth background color shifts, and a live star rating synced to the mood.

Published

Live Demo
Try it

The code

<div class="rs-04">
  <div class="rs-04__card">
    <div class="rs-04__heading">How do you feel today?</div>
    <div class="rs-04__sub">Drag to rate your experience</div>
    <div class="rs-04__emoji-wrap">
      <div class="rs-04__pulse" id="rs-04-pulse"></div>
      <div class="rs-04__face" id="rs-04-face">😐</div>
    </div>
    <div class="rs-04__mood-text" id="rs-04-mood-text" style="color:#f6d365">Neutral</div>
    <div class="rs-04__mood-sub" id="rs-04-mood-sub">Could be better, could be worse</div>
    <div class="rs-04__track-wrap">
      <div class="rs-04__track-bg"></div>
      <input class="rs-04__input" type="range" min="0" max="100" value="50" id="rs-04-inp">
    </div>
    <div class="rs-04__emojis-row">
      <span>😢</span><span>😕</span><span>😐</span><span>😊</span><span>😎</span>
    </div>
    <div class="rs-04__star-row" id="rs-04-stars">
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
    </div>
  </div>
</div>
.rs-04,
.rs-04 *,
.rs-04 *::before,
.rs-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.rs-04 ::selection {
  background: #ff6b35;
  color: #fff;
}

.rs-04 {
  --bg: #1a0a2e;
  --card: #241242;
  --border: #3d2070;
  --sad: #667eea;
  --ok: #f6d365;
  --happy: #ff6b35;
  --text: #f0e6ff;
  --muted: #7c5cad;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 24px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.rs-04__card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 36px 32px;
  width: 100%;
  max-width: 400px;
  text-align: center;
}

.rs-04__heading {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 6px;
}

.rs-04__sub {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 36px;
}

.rs-04__emoji-wrap {
  position: relative;
  width: 100px;
  height: 100px;
  margin: 0 auto 28px;
}

.rs-04__face {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 62px;
  line-height: 1;
  transition: background 0.4s, transform 0.3s;
  background: rgba(255,255,255,0.06);
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  position: relative;
}

.rs-04__pulse {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid currentColor;
  opacity: 0;
  transition: color 0.4s;
  animation: rs-04-pulse 1.8s ease-out infinite;
}

@keyframes rs-04-pulse {
  0% {
    transform: scale(0.9);
    opacity: 0.6;
  }

  100% {
    transform: scale(1.3);
    opacity: 0;
  }
}

.rs-04__mood-text {
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.5px;
  transition: color 0.4s;
  margin-bottom: 4px;
}

.rs-04__mood-sub {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 32px;
}

.rs-04__track-wrap {
  position: relative;
  padding: 14px 0;
  margin-bottom: 16px;
}

.rs-04__track-bg {
  height: 10px;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--sad), var(--ok), var(--happy));
  opacity: 0.3;
}

.rs-04__input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
  outline: none;
}

.rs-04__input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #ccc;
  box-shadow: 0 4px 16px rgba(0,0,0,0.4);
  transition: border-color 0.3s, transform 0.15s;
}

.rs-04__input:active::-webkit-slider-thumb {
  transform: scale(1.2);
}

.rs-04__input::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid #ccc;
  background: #fff;
  box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}

.rs-04__emojis-row {
  display: flex;
  justify-content: space-between;
  font-size: 18px;
  opacity: 0.5;
}

.rs-04__star-row {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 20px;
}

.rs-04__star {
  font-size: 20px;
  filter: grayscale(1);
  transition: filter 0.3s, transform 0.2s;
}

.rs-04__star.on {
  filter: grayscale(0);
  transform: scale(1.2);
}

@media (prefers-reduced-motion: reduce) {
  .rs-04__pulse {
    animation: none;
  }

  .rs-04__face,
    .rs-04__mood-text,
    .rs-04__star {
    transition: none;
  }
}
(function(){
  const inp = document.getElementById('rs-04-inp');
  const face = document.getElementById('rs-04-face');
  const pulse = document.getElementById('rs-04-pulse');
  const moodText = document.getElementById('rs-04-mood-text');
  const moodSub = document.getElementById('rs-04-mood-sub');
  const stars = document.querySelectorAll('.rs-04__star');
  const moods = [
    { emoji:'😢', text:'Terrible', sub:'This really isn\'t my day', color:'#667eea' },
    { emoji:'😕', text:'Not Great', sub:'Struggling a bit today', color:'#a78bfa' },
    { emoji:'😐', text:'Neutral', sub:'Could be better, could be worse', color:'#f6d365' },
    { emoji:'😊', text:'Good', sub:'Things are looking up!', color:'#fb923c' },
    { emoji:'😎', text:'Amazing!', sub:'Absolutely crushing it today', color:'#ff6b35' },
  ];
  function update(){
    const v = +inp.value;
    const idx = Math.min(4, Math.floor(v/100*5));
    const m = moods[idx];
    face.textContent = m.emoji;
    face.style.background = m.color+'22';
    pulse.style.color = m.color;
    moodText.style.color = m.color;
    moodText.textContent = m.text;
    moodSub.textContent = m.sub;
    const activeStars = Math.round(v/100*5);
    stars.forEach((s,i)=> s.classList.toggle('on', i < activeStars));
  }
  inp.addEventListener('input', update);
  update();
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Range Slider from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Custom Emoji Mood Selector
Source: https://codefronts.com/components/css-range-sliders/custom-emoji-mood-selector/

An interactive feedback slider that transitions between five emoji states from sad to ecstatic, with a pulsing ring animation, smooth background color shifts, and a live star rating synced to the mood.
## HTML
```html
<div class="rs-04">
  <div class="rs-04__card">
    <div class="rs-04__heading">How do you feel today?</div>
    <div class="rs-04__sub">Drag to rate your experience</div>
    <div class="rs-04__emoji-wrap">
      <div class="rs-04__pulse" id="rs-04-pulse"></div>
      <div class="rs-04__face" id="rs-04-face">😐</div>
    </div>
    <div class="rs-04__mood-text" id="rs-04-mood-text" style="color:#f6d365">Neutral</div>
    <div class="rs-04__mood-sub" id="rs-04-mood-sub">Could be better, could be worse</div>
    <div class="rs-04__track-wrap">
      <div class="rs-04__track-bg"></div>
      <input class="rs-04__input" type="range" min="0" max="100" value="50" id="rs-04-inp">
    </div>
    <div class="rs-04__emojis-row">
      <span>😢</span><span>😕</span><span>😐</span><span>😊</span><span>😎</span>
    </div>
    <div class="rs-04__star-row" id="rs-04-stars">
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
      <span class="rs-04__star">⭐</span>
    </div>
  </div>
</div>
```
## CSS
```css
.rs-04,
.rs-04 *,
.rs-04 *::before,
.rs-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.rs-04 ::selection {
  background: #ff6b35;
  color: #fff;
}

.rs-04 {
  --bg: #1a0a2e;
  --card: #241242;
  --border: #3d2070;
  --sad: #667eea;
  --ok: #f6d365;
  --happy: #ff6b35;
  --text: #f0e6ff;
  --muted: #7c5cad;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 24px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.rs-04__card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 36px 32px;
  width: 100%;
  max-width: 400px;
  text-align: center;
}

.rs-04__heading {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 6px;
}

.rs-04__sub {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 36px;
}

.rs-04__emoji-wrap {
  position: relative;
  width: 100px;
  height: 100px;
  margin: 0 auto 28px;
}

.rs-04__face {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 62px;
  line-height: 1;
  transition: background 0.4s, transform 0.3s;
  background: rgba(255,255,255,0.06);
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  position: relative;
}

.rs-04__pulse {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid currentColor;
  opacity: 0;
  transition: color 0.4s;
  animation: rs-04-pulse 1.8s ease-out infinite;
}

@keyframes rs-04-pulse {
  0% {
    transform: scale(0.9);
    opacity: 0.6;
  }

  100% {
    transform: scale(1.3);
    opacity: 0;
  }
}

.rs-04__mood-text {
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.5px;
  transition: color 0.4s;
  margin-bottom: 4px;
}

.rs-04__mood-sub {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 32px;
}

.rs-04__track-wrap {
  position: relative;
  padding: 14px 0;
  margin-bottom: 16px;
}

.rs-04__track-bg {
  height: 10px;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--sad), var(--ok), var(--happy));
  opacity: 0.3;
}

.rs-04__input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
  outline: none;
}

.rs-04__input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #ccc;
  box-shadow: 0 4px 16px rgba(0,0,0,0.4);
  transition: border-color 0.3s, transform 0.15s;
}

.rs-04__input:active::-webkit-slider-thumb {
  transform: scale(1.2);
}

.rs-04__input::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid #ccc;
  background: #fff;
  box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}

.rs-04__emojis-row {
  display: flex;
  justify-content: space-between;
  font-size: 18px;
  opacity: 0.5;
}

.rs-04__star-row {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 20px;
}

.rs-04__star {
  font-size: 20px;
  filter: grayscale(1);
  transition: filter 0.3s, transform 0.2s;
}

.rs-04__star.on {
  filter: grayscale(0);
  transform: scale(1.2);
}

@media (prefers-reduced-motion: reduce) {
  .rs-04__pulse {
    animation: none;
  }

  .rs-04__face,
    .rs-04__mood-text,
    .rs-04__star {
    transition: none;
  }
}
```

## JavaScript
```js
(function(){
  const inp = document.getElementById('rs-04-inp');
  const face = document.getElementById('rs-04-face');
  const pulse = document.getElementById('rs-04-pulse');
  const moodText = document.getElementById('rs-04-mood-text');
  const moodSub = document.getElementById('rs-04-mood-sub');
  const stars = document.querySelectorAll('.rs-04__star');
  const moods = [
    { emoji:'😢', text:'Terrible', sub:'This really isn\'t my day', color:'#667eea' },
    { emoji:'😕', text:'Not Great', sub:'Struggling a bit today', color:'#a78bfa' },
    { emoji:'😐', text:'Neutral', sub:'Could be better, could be worse', color:'#f6d365' },
    { emoji:'😊', text:'Good', sub:'Things are looking up!', color:'#fb923c' },
    { emoji:'😎', text:'Amazing!', sub:'Absolutely crushing it today', color:'#ff6b35' },
  ];
  function update(){
    const v = +inp.value;
    const idx = Math.min(4, Math.floor(v/100*5));
    const m = moods[idx];
    face.textContent = m.emoji;
    face.style.background = m.color+'22';
    pulse.style.color = m.color;
    moodText.style.color = m.color;
    moodText.textContent = m.text;
    moodSub.textContent = m.sub;
    const activeStars = Math.round(v/100*5);
    stars.forEach((s,i)=> s.classList.toggle('on', i < activeStars));
  }
  inp.addEventListener('input', update);
  update();
})();
```

How this works

The slider maps its 0–100 range into five buckets using Math.floor(v/100*5) clamped to index 4, each bucket resolving to an emoji, label, subtext, and accent color from a moods[] data array. CSS transitions on .rs-04__face background and the mood text color create the smooth hue shift between states. The pulsing ring uses a @keyframes rs-04-pulse with scale(0.9) → scale(1.3) and opacity fade, colorized via currentColor so it automatically inherits the active mood color.

The five-star row uses classList.toggle('on',...) driven by Math.round(v/100*5) to fill stars proportionally to the slider value. The track itself carries a fixed linear-gradient from blue through yellow to orange-red across its full width, providing instant visual context for the emotional range without any JS involvement.

Make it yours

  • Add more granularity by expanding the moods[] array to 7 or 10 items and adjusting the bucket formula from Math.floor(v/100*5) to Math.floor(v/100*moods.length).
  • Use custom SVG faces instead of emoji by replacing face.textContent = m.emoji with an innerHTML assignment pointing to inline SVG paths for pixel-perfect cross-platform rendering.
  • Replace the star row with a 1–10 NPS numeric scale by rendering 10 number buttons and toggling a filled class based on the slider value divided by 10.
  • Add haptic feedback on mobile by calling navigator.vibrate(20) each time the mood bucket changes (detectable by comparing previous and current index in the update function).
  • Animate the emoji on state change using a quick transform: scale(0.8) → scale(1) CSS transition triggered by adding and removing a class via JS requestAnimationFrame.

Gotchas — read before shipping

  • Emoji rendering is highly OS-dependent — the same codepoint looks dramatically different on Windows, macOS, and Android; test the full range on target platforms and consider SVG icons for controlled environments.
  • The pulsing ring animation runs continuously with animation-iteration-count: infinite — on battery-sensitive devices this counts as a continuous paint; pause via animation-play-state: paused when the component is off-screen.
  • The background gradient on .rs-04__track-bg is decorative only — the actual interactive track is fully transparent, so avoid adding background to the <input> directly or it will override the custom track div.

Browser support

ChromeSafariFirefoxEdge
80+14+78+80+

Emoji rendering varies significantly by OS version; test across Windows, macOS, and mobile platforms for visual consistency.

Techniques used in this demo

Search CodeFronts

Loading…