emoteer

Feedback widget

An emoji intensity slider that collects a rating with one gesture

A compact feedback widget built around ReactionSlider. The user drags a single emoji thumb; the value maps to a 1–5 rating that updates the emoji live and is captured on submit.

How was your experience?

3 / 5

The pattern

components/feedback-widget.tsx
import { ReactionSlider } from '@emoteer/react';
import { useState } from 'react';

const LEVELS = ['😞', '🙁', '😐', '🙂', '🤩'];

export function FeedbackWidget() {
  const [value, setValue] = useState(50);
  const [submitted, setSubmitted] = useState<number | null>(null);
  const level = Math.max(0, Math.min(4, Math.floor(value / 20)));
  const emoji = LEVELS[level];

  return (
    <div className="flex flex-col gap-4">
      <div className="flex items-center justify-between">
        <h3 className="text-sm font-semibold tracking-tight">
          How was your experience?
        </h3>
        <span className="font-mono text-xs text-muted-foreground">
          {level + 1} / 5
        </span>
      </div>

      <ReactionSlider.Root emoji={emoji} value={value} onChange={setValue}>
        <ReactionSlider.Track>
          <ReactionSlider.Range />
        </ReactionSlider.Track>
        <ReactionSlider.Thumb>
          <ReactionSlider.Marker />
        </ReactionSlider.Thumb>
      </ReactionSlider.Root>

      <button
        type="button"
        onClick={() => setSubmitted(level + 1)}
        className="rounded-full bg-foreground px-4 py-2 text-sm font-semibold text-background"
      >
        Send feedback
      </button>

      {submitted !== null && (
        <p className="text-xs text-muted-foreground">
          Submitted rating:{' '}
          <span className="font-mono text-foreground">{submitted} / 5</span>
        </p>
      )}
    </div>
  );
}
components/FeedbackWidget.svelte
<script lang="ts">
  import { ReactionSlider } from '@emoteer/svelte';

  const LEVELS = ['😞', '🙁', '😐', '🙂', '🤩'];

  let value = $state(50);
  let submitted = $state<number | null>(null);
  const level = $derived(Math.max(0, Math.min(4, Math.floor(value / 20))));
  const emoji = $derived(LEVELS[level]);
</script>

<div class="flex flex-col gap-4">
  <div class="flex items-center justify-between">
    <h3 class="text-sm font-semibold tracking-tight">How was your experience?</h3>
    <span class="font-mono text-xs text-muted-foreground">{level + 1} / 5</span>
  </div>

  <ReactionSlider.Root {emoji} {value} onChange={(v) => (value = v)}>
    <ReactionSlider.Track>
      <ReactionSlider.Range />
    </ReactionSlider.Track>
    <ReactionSlider.Thumb>
      <ReactionSlider.Marker />
    </ReactionSlider.Thumb>
  </ReactionSlider.Root>

  <button
    type="button"
    onclick={() => (submitted = level + 1)}
    class="rounded-full bg-foreground px-4 py-2 text-sm font-semibold text-background"
  >
    Send feedback
  </button>

  {#if submitted !== null}
    <p class="text-xs text-muted-foreground">
      Submitted rating:
      <span class="font-mono text-foreground">{submitted} / 5</span>
    </p>
  {/if}
</div>

What it does

  • A single slider captures a 0–100 value.
  • The value is bucketed into one of five levels (1–5) and mapped to an emoji that updates live as the user drags.
  • On submit, the chosen 1–5 rating is stored in submitted and shown below.

Notes

  • The demo keeps the result in local submitted state — call an onSubmit(rating) prop instead to send it upstream.
  • Replace the emoji array with brand-specific emojis for product satisfaction, NPS-like surveys, or mood check-ins.
  • For a 1–5 slider without bucketing, set min={1} and max={5} on the root and let the thumb snap to integers with step={1}.

On this page