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
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>
);
}<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
submittedand shown below.
Notes
- The demo keeps the result in local
submittedstate — call anonSubmit(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}andmax={5}on the root and let the thumb snap to integers withstep={1}.