Zoeken...  ⌘K GitHub

Toggle UI Elements

Toggle switch (aan/uit). Toegankelijk met role="switch". 3 groottes, optioneel label.

/toggle
src/components/ui/Toggle.astro
---
/**
 * Toggle / Switch
 * Toegankelijke on/off switch. Label links of rechts.
 * 3 groottes, kleur via CSS vars.
 */
interface Props {
  id?: string;
  name?: string;
  label?: string;
  labelPosition?: 'left' | 'right';
  checked?: boolean;
  disabled?: boolean;
  size?: 'sm' | 'md' | 'lg';
  description?: string;
}

const {
  id,
  name,
  label,
  labelPosition = 'right',
  checked = false,
  disabled = false,
  size = 'md',
  description,
} = Astro.props;

const toggleId = id ?? `toggle-${Math.random().toString(36).slice(2, 7)}`;
---

<div class:list={['tg', `tg--${size}`, `tg--label-${labelPosition}`, { 'tg--disabled': disabled }]} data-component="toggle">
  <label class="tg__label-wrap" for={toggleId}>
    {label && labelPosition === 'left' && (
      <div class="tg__text">
        <span class="tg__label">{label}</span>
        {description && <span class="tg__desc">{description}</span>}
      </div>
    )}

    <input
      type="checkbox"
      id={toggleId}
      name={name}
      class="tg__input"
      checked={checked}
      disabled={disabled}
      role="switch"
      aria-checked={checked ? 'true' : 'false'}
    />
    <span class="tg__track" aria-hidden="true">
      <span class="tg__thumb"></span>
    </span>

    {label && labelPosition === 'right' && (
      <div class="tg__text">
        <span class="tg__label">{label}</span>
        {description && <span class="tg__desc">{description}</span>}
      </div>
    )}
  </label>
</div>

<script>
  document.querySelectorAll('[data-component="toggle"]').forEach(el => {
    const input = el.querySelector<HTMLInputElement>('.tg__input');
    if (input) {
      input.addEventListener('change', () => {
        input.setAttribute('aria-checked', input.checked ? 'true' : 'false');
      });
    }
  });
</script>

<style>
  .tg { display: inline-block; }

  .tg__label-wrap {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    cursor: pointer;
    user-select: none;
  }

  .tg--disabled .tg__label-wrap { cursor: not-allowed; opacity: 0.5; }

  /* Hide native checkbox */
  .tg__input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    margin: 0;
  }

  /* Track */
  .tg__track {
    position: relative;
    flex-shrink: 0;
    background: rgba(0,0,0,0.15);
    border-radius: 999px;
    transition: background 0.2s;
    display: flex;
    align-items: center;
  }

  /* Sizes */
  .tg--sm .tg__track  { width: 32px; height: 18px; }
  .tg--md .tg__track  { width: 44px; height: 24px; }
  .tg--lg .tg__track  { width: 56px; height: 30px; }

  /* Thumb */
  .tg__thumb {
    position: absolute;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(0,0,0,0.25);
    transition: transform 0.2s cubic-bezier(0.4,0,0.2,1);
  }

  .tg--sm .tg__thumb { width: 14px; height: 14px; left: 2px; }
  .tg--md .tg__thumb { width: 18px; height: 18px; left: 3px; }
  .tg--lg .tg__thumb { width: 24px; height: 24px; left: 3px; }

  /* Checked state */
  .tg__input:checked + .tg__track {
    background: var(--color-accent, #6366f1);
  }

  .tg--sm .tg__input:checked + .tg__track .tg__thumb { transform: translateX(14px); }
  .tg--md .tg__input:checked + .tg__track .tg__thumb { transform: translateX(20px); }
  .tg--lg .tg__input:checked + .tg__track .tg__thumb { transform: translateX(26px); }

  /* Focus ring */
  .tg__input:focus-visible + .tg__track {
    outline: 2px solid var(--color-accent, #6366f1);
    outline-offset: 2px;
  }

  /* Text */
  .tg__text { display: flex; flex-direction: column; gap: 0.125rem; }
  .tg__label { font-size: 0.9375rem; font-weight: 600; color: var(--color-primary, #0a0a0a); }
  .tg--sm .tg__label { font-size: 0.8125rem; }
  .tg--lg .tg__label { font-size: 1rem; }
  .tg__desc { font-size: 0.8125rem; color: var(--color-muted, #6b7280); }
</style>

Props

Prop Type Default Beschrijving
label string Label naast de toggle
checked boolean false Standaard ingeschakeld
name string Form name attribuut
disabled boolean false Uitgeschakeld
size 'sm' | 'md' | 'lg' 'md' Grootte

* = verplicht