{ CF }

20 Pure CSS Toggles & Switches

Permission Stack

Grouped consent UI with mini pill toggles inside stacked bordered rows. Each row independently grants or revokes a permission — the icon and label brighten and the pill switches to brand-green when granted. The dominant trust-first UI pattern replacing modal popups.

Pure CSS MIT licensed

Permission Stack the 6th of 20 designs in the 20 Pure CSS Toggles & Switches collection. The design is implemented in pure CSS — no JavaScript required. Copy the HTML and CSS panels below into your project. Because the demo is pure CSS, it works in any framework or templating engine you happen to use. The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.

Live preview

Open in playground

The code

<div class="tg-perm">
  <label class="tg-perm-item">
    <input class="tg-perm-input" type="checkbox" checked>
    <svg class="tg-perm-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
      <circle cx="12" cy="10" r="3"/>
    </svg>
    <span class="tg-perm-info">
      <span class="tg-perm-name">Location</span>
      <span class="tg-perm-desc">While using the app</span>
    </span>
    <span class="tg-perm-pill" aria-hidden="true">
      <span class="tg-perm-thumb"></span>
    </span>
  </label>
  <label class="tg-perm-item">
    <input class="tg-perm-input" type="checkbox">
    <svg class="tg-perm-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M23 7l-7 5 7 5V7z"/>
      <rect x="1" y="5" width="15" height="14" rx="2"/>
    </svg>
    <span class="tg-perm-info">
      <span class="tg-perm-name">Camera</span>
      <span class="tg-perm-desc">Photos and video</span>
    </span>
    <span class="tg-perm-pill" aria-hidden="true">
      <span class="tg-perm-thumb"></span>
    </span>
  </label>
  <label class="tg-perm-item">
    <input class="tg-perm-input" type="checkbox" checked>
    <svg class="tg-perm-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <rect x="9" y="2" width="6" height="12" rx="3"/>
      <path d="M19 10a7 7 0 0 1-14 0"/>
      <path d="M12 18v3"/>
    </svg>
    <span class="tg-perm-info">
      <span class="tg-perm-name">Microphone</span>
      <span class="tg-perm-desc">Voice input</span>
    </span>
    <span class="tg-perm-pill" aria-hidden="true">
      <span class="tg-perm-thumb"></span>
    </span>
  </label>
  <label class="tg-perm-item">
    <input class="tg-perm-input" type="checkbox">
    <svg class="tg-perm-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
      <circle cx="9" cy="7" r="4"/>
      <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
      <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
    </svg>
    <span class="tg-perm-info">
      <span class="tg-perm-name">Contacts</span>
      <span class="tg-perm-desc">Read access only</span>
    </span>
    <span class="tg-perm-pill" aria-hidden="true">
      <span class="tg-perm-thumb"></span>
    </span>
  </label>
</div>
.tg-perm {
  --tg-perm-bg: #08080f;
  --tg-perm-rim: #14141e;
  --tg-perm-wire: #1e1e2e;
  --tg-perm-fog: #3a3a52;
  --tg-perm-ash: #7a7a98;
  --tg-perm-snow: #f0f0fa;
  --tg-perm-volt: #e0ff00;
  --tg-perm-void: #04040a;
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 280px;
  font-family: "Inter", "Segoe UI", system-ui, sans-serif;
}
.tg-perm-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--tg-perm-wire);
  margin-top: -1px;
  background: var(--tg-perm-bg);
  cursor: pointer;
  transition: background 0.2s ease;
}
.tg-perm-item:first-child { border-radius: 12px 12px 0 0; }
.tg-perm-item:last-child  { border-radius: 0 0 12px 12px; }
.tg-perm-item:hover { background: var(--tg-perm-rim); }
.tg-perm-input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0;
}
.tg-perm-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--tg-perm-ash);
  opacity: 0.4;
  transition: opacity 0.3s ease, color 0.3s ease;
}
.tg-perm-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.tg-perm-name {
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--tg-perm-snow);
  opacity: 0.5;
  transition: opacity 0.3s ease;
}
.tg-perm-desc {
  font-size: 11px;
  color: var(--tg-perm-ash);
}
/* Mini pill toggle on the right of each row. */
.tg-perm-pill {
  position: relative;
  width: 38px;
  height: 20px;
  flex-shrink: 0;
  border-radius: 10px;
  background: var(--tg-perm-wire);
  border: 1px solid var(--tg-perm-fog);
  transition: background 0.3s ease, border-color 0.3s ease;
}
.tg-perm-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--tg-perm-fog);
  transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1), background 0.3s ease;
}
.tg-perm-input:checked ~ .tg-perm-icon { opacity: 1; color: var(--tg-perm-volt); }
.tg-perm-input:checked ~ .tg-perm-info .tg-perm-name { opacity: 1; }
.tg-perm-input:checked ~ .tg-perm-pill {
  background: var(--tg-perm-volt);
  border-color: var(--tg-perm-volt);
}
.tg-perm-input:checked ~ .tg-perm-pill .tg-perm-thumb {
  transform: translateX(18px);
  background: var(--tg-perm-void);
}
.tg-perm-input:focus-visible ~ .tg-perm-pill {
  outline: 2px solid var(--tg-perm-volt);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .tg-perm-item,
  .tg-perm-icon,
  .tg-perm-name,
  .tg-perm-pill,
  .tg-perm-thumb { transition: none; }
}

Search CodeFronts

Loading…