JSFiddle - React, Tailwind, and code Playground

by Patrick Ludewig

HTML

<div class="country-language-selector">
  <!-- Trigger button for the selector -->
  <button id="selector-trigger" class="selector-trigger" aria-label="Select country and language">
    <span class="flag">
      {% if localization.country.iso_code == 'AT' %}
        🇦🇹
      {% elsif localization.country.iso_code == 'DE' %}
        🇩🇪
      {% elsif localization.country.iso_code == 'CH' %}
        🇨🇭
      {% elsif localization.country.iso_code == 'FR' %}
        🇫🇷
      {% else %}
        🌐
      {% endif %}
    </span>
    <span class="country-name">{{ localization.country.name }}</span>
    <span class="separator">|</span>
    <span class="language-name">{{ localization.language.endonym_name | capitalize }}</span>
    <svg class="chevron-icon" width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M1 1L5 5L9 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
  </button>

  <!-- Desktop Modal -->
  <div id="desktop-modal" class="selector-modal desktop-modal" role="dialog" aria-modal="true" aria-labelledby="desktop-title">
    <div class="modal-content">
      <div class="modal-header">
        <button class="close-button" aria-label="Close">
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </button>
      </div>
      
      <div class="modal-body">
        <div class="country-section">
          <h2 id="desktop-country-title" class="section-title">Lieferland</h2>
          <p class="section-description">Wähle das Land aus, in dem deine Lieferung gehen soll.</p>
          
          <form id="desktop-country-form" class="country-form">
            {% for country in localization.available_countries %}
              <div class="option-item">
           ...

CSS

/* Base styles */
.country-language-selector {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: #000;
  position: relative;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

/* Trigger button */
.selector-trigger {
  display: flex;
  align-items: center;
  background: none;
  border: none;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 14px;
}

.selector-trigger .flag {
  margin-right: 8px;
  font-size: 18px;
}

.selector-trigger .separator {
  margin: 0 8px;
  color: #666;
}

.selector-trigger .chevron-icon {
  margin-left: 8px;
}

/* Modal base styles */
.selector-modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 1000;
  overflow: auto;
}

.selector-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-content {
  background-color: #fff;
  max-width: 800px;
  width: 100%;
  margin: 0 auto;
  position: relative;
  padding: 32px;
}

.modal-header {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 16px;
}

.close-button {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
}

.section-title {
  font-size: 24px;
  font-weight: bold;
  margin: 0 0 8px 0;
}

.section-description {
  font-size: 14px;
  color: #333;
  margin: 0 0 16px 0;
}

/* Option items */
.option-item {
  margin-bottom: 8px;
}

.option-label {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  cursor: pointer;
  border-radius: 2px;
  transition: background-color 0.2s;
}

.country-form .option-label {
  background-color: #fff;
}

.language-form .option-label {
  background-color: #f5f5f5;
}

.option-label .flag {
  font-size: 24px;
  margin-right: 12px;
}

.option-text {
  flex-grow: 1;
}

.check-icon {
  display:...

JavaScript

document.addEventListener('DOMContentLoaded', function() {
  // Elements
  const selectorTrigger = document.getElementById('selector-trigger');
  const desktopModal = document.getElementById('desktop-modal');
  const mobileCountryModal = document.getElementById('mobile-country-modal');
  const mobileLanguageModal = document.getElementById('mobile-language-modal');
  const closeButtons = document.querySelectorAll('.close-button');
  const desktopSubmitButton = document.getElementById('desktop-submit');
  const mobileSubmitButton = document.getElementById('mobile-submit');
  
  // Country and language forms
  const desktopCountryForm = document.getElementById('desktop-country-form');
  const desktopLanguageForm = document.getElementById('desktop-language-form');
  const mobileCountryForm = document.getElementById('mobile-country-form');
  const mobileLanguageForm = document.getElementById('mobile-language-form');
  
  // Check if we're on mobile
  const isMobile = () => window.innerWidth < 768;
  
  // Open the appropriate modal
  selectorTrigger.addEventListener('click', function() {
    if (isMobile()) {
      mobileCountryModal.classList.add('active');
    } else {
      desktopModal.classList.add('active');
    }
  });
  
  // Close all modals
  function closeAllModals() {
    desktopModal.classList.remove('active');
    mobileCountryModal.classList.remove('active');
    mobileLanguageModal.classList.remove('active');
  }
  
  // Close button event listeners
  closeButtons.forEach(button => {
    button.addEventListener('click', closeAllModals);
  });
  
  // Close modal when clicking outside
  window.addEventListener('click', function(event) {
    if (event.target === desktopModal || 
        event.target === mobileCountryModal || 
        event.target === mobileLanguageModal) {
      closeAllModals();
    }
  });
  
  // Handle country selection in mobile view
  const mobileCountryInputs = mobileCountryForm.querySelectorAll('input[type="radio"]');
 ...