Building a Scalable and Responsive Spice Level Selector Component in React with Real-Time Feedback

Creating a scalable and responsive spice level selector in React is essential for food and hot sauce apps that need to provide instantaneous feedback on users' heat preferences. This guide walks you through building a React Spice Level Selector that not only responds immediately to user interactions but also scales easily with customizable ranges, supports accessibility, and offers real-time feedback, elevating the user experience.


Table of Contents

  1. Defining the Spice Level Selector Requirements
  2. Designing a Scalable and Responsive UI
  3. Setting Up React Environment
  4. Implementing the Core Selector Component
  5. Making the Component Scalable and Customizable
  6. Adding Responsive Design for Mobile and Desktop
  7. Providing Real-Time Visual Feedback
  8. Enhancing Accessibility and Keyboard Support
  9. Integrating Real-Time Polling with Zigpoll
  10. Performance Optimization Techniques
  11. Future Enhancements and Extensions

Defining the Spice Level Selector Requirements

To create an effective spice level selector, ensure it fulfills these core needs:

  • Interactive Scale: Allows users to select spice levels, e.g., 1 (mild) to 10 (extreme heat).
  • Real-Time Updates: UI updates immediately on user selection.
  • Scalable: Supports customizable max levels and labels without code overhaul.
  • Responsive Layout: Works flawlessly across devices (mobile, tablet, desktop).
  • Accessibility: Keyboard navigation and screen reader support.
  • Optional Integration: Ability to gather user feedback via polling, such as using Zigpoll.
  • Visual Feedback: Display dynamic indicators like heat meters or color changes.

Designing a Scalable and Responsive UI

Choose either:

  • Icon-based selector (e.g., clickable chili icons) with visual states.
  • Slider-based interface for continuous level selection.

Using react-icons with chili peppers (GiChiliPepper) creates an engaging visual scale.

Add descriptive labels (e.g., 'Mild', 'Extreme') to communicate heat intensity and tooltips for clarity.


Setting Up React Environment

Create your React app with:

npx create-react-app spice-level-selector
cd spice-level-selector
npm install styled-components react-icons

Use React v18+ for modern hooks support.


Implementing the Core Selector Component

Create a UI with interactive chili icons that update selected heat level instantly.

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { GiChiliPepper } from 'react-icons/gi';

const Container = styled.div`
  max-width: 420px;
  margin: 0 auto;
  text-align: center;
`;

const IconRow = styled.div`
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
`;

const ChiliIcon = styled(GiChiliPepper)`
  cursor: pointer;
  color: ${(props) => (props.active ? '#E63946' : '#CCCCCC')};
  transform: scale(${(props) => (props.active ? 1.25 : 1)});
  transition: color 0.25s, transform 0.25s;
  &:hover,
  &:focus {
    color: #F94144;
    transform: scale(1.4);
    outline: none;
  }
`;

const Label = styled.div`
  margin-top: 12px;
  font-weight: 600;
  font-size: 1.1rem;
  color: #222;
`;

const heatDescriptions = [
  'No Heat',
  'Very Mild',
  'Mild',
  'Medium Mild',
  'Medium',
  'Medium Hot',
  'Hot',
  'Very Hot',
  'Extra Hot',
  'Extreme',
];

export default function SpiceLevelSelector({
  maxLevel = 10,
  labels = heatDescriptions,
  value = null,
  onChange = () => {},
}) {
  const [selectedLevel, setSelectedLevel] = useState(value || 0);

  useEffect(() => {
    if (value !== null && value !== selectedLevel) {
      setSelectedLevel(value);
    }
  }, [value, selectedLevel]);

  const handleSelection = (level) => {
    setSelectedLevel(level);
    onChange(level);
  };

  const handleKeyDown = (e, level) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleSelection(level);
    }
  };

  return (
    <Container role="group" aria-label="Spice level selector">
      <IconRow>
        {[...Array(maxLevel)].map((_, idx) => {
          const level = idx + 1;
          return (
            <ChiliIcon
              key={level}
              size={32}
              role="button"
              tabIndex={0}
              active={level <= selectedLevel}
              aria-pressed={level === selectedLevel}
              title={`${level}: ${labels[idx] || `Level ${level}`}`}
              aria-label={`Select spice level ${level} - ${
                labels[idx] || `Level ${level}`
              }`}
              onClick={() => handleSelection(level)}
              onKeyDown={(e) => handleKeyDown(e, level)}
            />
          );
        })}
      </IconRow>
      <Label>
        {selectedLevel === 0
          ? 'Select your spice level'
          : `${selectedLevel} - ${labels[selectedLevel - 1] || selectedLevel}`}
      </Label>
    </Container>
  );
}

Making the Component Scalable and Customizable

  • Custom max levels: Pass the maxLevel prop to adjust scale dynamically.
  • Custom labels: Override labels prop with descriptive text matching your range or branding.
  • Controlled mode: Support value and onChange props for external state management.

Example usage:

const customLabels = [
  'No Heat',
  'Mild',
  'Medium',
  'Hot',
  'Extra Hot',
  'Blazing',
  'Inferno',
];

<SpiceLevelSelector
  maxLevel={7}
  labels={customLabels}
  value={currentLevel}
  onChange={setCurrentLevel}
/>;

Connect Zigpoll to your stack.Sync survey responses to the tools you already use — no code required.
See integrations

Adding Responsive Design for Mobile and Desktop

Using styled-components media queries, adjust icon sizes and layout:

const IconRow = styled.div`
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  max-width: 420px;
  margin: 0 auto;
`;

const ChiliIcon = styled(GiChiliPepper)`
  cursor: pointer;
  color: ${(props) => (props.active ? '#E63946' : '#CCCCCC')};
  transition: color 0.25s, transform 0.25s;
  transform: scale(${(props) => (props.active ? 1.25 : 1)});

  &:hover,
  &:focus {
    color: #F94144;
    transform: scale(1.4);
    outline: none;
  }

  @media (max-width: 600px) {
    width: 28px !important;
    height: 28px !important;
  }

  @media (max-width: 400px) {
    width: 24px !important;
    height: 24px !important;
  }
`;

This ensures your selector remains usable and visually appealing on all devices.


Providing Real-Time Visual Feedback

Instant feedback improves user confidence when selecting spice levels. Enhance the UI with a heat meter bar representing the selected level number:

const HeatMeterContainer = styled.div`
  margin-top: 16px;
  max-width: 420px;
  height: 14px;
  background: #eee;
  border-radius: 7px;
  overflow: hidden;
  margin-left: auto;
  margin-right: auto;
`;

const HeatLevelBar = styled.div`
  height: 100%;
  width: ${(props) => (props.level / props.maxLevel) * 100}%;
  background: linear-gradient(90deg, #ff6b6b, #d90429);
  transition: width 0.3s ease;
`;

function HeatMeter({ level, maxLevel }) {
  return (
    <HeatMeterContainer aria-label={`Heat level at ${level} out of ${maxLevel}`}>
      <HeatLevelBar level={level} maxLevel={maxLevel} />
    </HeatMeterContainer>
  );
}

Include <HeatMeter level={selectedLevel} maxLevel={maxLevel} /> below the icon row in your SpiceLevelSelector component. This visually fills up the heat meter based on the level selected.

Additional real-time feedback ideas:

  • Change background or text color dynamically using heat intensity.
  • Show descriptive hints or animations (e.g., heat waves using CSS or libraries like Lottie).

Enhancing Accessibility and Keyboard Support

Boost usability by:

  • Adding tabIndex={0} for keyboard focus on chili icons.
  • Implementing keyboard event handlers to select levels via Enter or Space keys.
  • Using ARIA roles and attributes (role="button", aria-pressed) to aid screen readers.
  • Applying semantic containers (role="group") labeling the selector.

Keyboard support snippet from above:

const handleKeyDown = (e, level) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    handleSelection(level);
  }
};

This practice enhances the user experience for all users, including those relying on assistive technologies.


Integrating Real-Time Polling with Zigpoll

Elevate your spice selector with real-time user insights by integrating the Zigpoll polling API.

Why integrate Zigpoll?

  • Collect live data on heat preferences.
  • Run engaging polls like "What’s your favorite spice level?"
  • Display dynamic leaderboards or trending heat levels to guide users.
  • Make your app more interactive and data-driven.

Steps to integrate:

  1. Create a poll on zigpoll.com.
  2. Use their REST API or embed widgets in your React app.
  3. Fetch poll results and display alongside your spice selector.

Example snippet fetching spice level poll data:

import React, { useEffect, useState } from 'react';

function SpicePollResults() {
  const [results, setResults] = useState(null);

  useEffect(() => {
    fetch('https://api.zigpoll.com/polls/spice-level/results', {
      headers: {
        Authorization: 'Bearer YOUR_API_KEY',
      },
    })
      .then((res) => res.json())
      .then(setResults)
      .catch((e) => console.error('Failed to load poll results', e));
  }, []);

  if (!results) return <p>Loading spice poll results...</p>;

  return (
    <ul>
      {results.map(({ level, votes }) => (
        <li key={level}>
          Level {level}: {votes} vote{votes !== 1 ? 's' : ''}
        </li>
      ))}
    </ul>
  );
}

Display poll data to give users community insights or adapt default values dynamically.


Performance Optimization Techniques

  • Wrap SpiceLevelSelector or inner components in React.memo to avoid unnecessary re-renders.
  • Debounce or throttle external API calls triggered on onChange to avoid performance bottlenecks.
  • Lazy load complex animations or external data only when necessary.
  • Use semantic HTML and optimized CSS-in-JS patterns for faster rendering.

Future Enhancements and Extensions

  • Multi-heat selection: Allow users to select multiple spice levels or blend heat preferences.
  • Local storage save: Remember users’ spice level selections across sessions with localStorage.
  • Global state sync: Use Redux or Zustand to share the spice level across your app.
  • Flavor pairings: Add flavor notes or recommended dishes per heat level.
  • Animated heat icons: Use Lottie or CSS animations for immersive feedback.

Related Resources and Links


Implement this scalable and responsive React spice level selector in your app to provide your users with instant, intuitive, and engaging real-time feedback on their hot sauce heat preferences — all optimized for performance, accessibility, and future growth. Spice up your React projects today! 🌶️🔥

Start collecting feedback in 5 minutes.

Try our no-code surveys that visitors actually answer.

Questions or Feedback?

We are always ready to hear from you.