Tailwind Forms Plugin Playground

by tailkits

HTML

<!-- Tailwind config + CDN -->
<script>
  // Needed so the "dark:" variants work with a toggle
  tailwind = { config: { darkMode: "class" } }
</script>
<script src="https://cdn.tailwindcss.com"></script>

<section
  id="tw-forms-playground"
  class="twfp forms-base antialiased text-slate-800 bg-white"
>
  <div class="max-w-6xl mx-auto p-6">
    <header class="mb-6">
      <h2 class="text-2xl font-bold">Tailwind Forms Plugin Playground</h2>
      <p class="muted mt-1 text-sm">
        Toggle between a simulated <b>base reset</b> and <b>class strategy</b>.
        In production, load the real plugin via Tailwind v4
        <code>@plugin</code> or v3 <code>plugins</code> config.
      </p>
    </header>

    <!-- Controls -->
    <div class="flex flex-wrap items-center gap-4 mb-6">
      <label class="flex items-center gap-2 cursor-pointer">
        <input id="twfp-modeToggle" type="checkbox" class="h-4 w-4" />
        <span class="text-sm"
          >Use <b>class</b> strategy (off = base reset)</span
        >
      </label>
      <label class="flex items-center gap-2 cursor-pointer">
        <input id="twfp-darkToggle" type="checkbox" class="h-4 w-4" />
        <span class="text-sm">Dark mode</span>
      </label>
      <button
        id="twfp-copyBtn"
        type="button"
        class="text-sm rounded-md border px-3 py-1 hover:bg-slate-50 dark:hover:bg-slate-800"
      >
        Copy sample form HTML
      </button>
    </div>

    <div id="twfp-playground" class="grid gap-6 sm:grid-cols-2">
      <!-- Base strategy panel -->
      <section
        id="twfp-panelBase"
        class="demo-card rounded-2xl border border-slate-200 dark:border-slate-700 bg-white p-5 dark:bg-slate-800"
      >
        <h3 class="text-lg font-semibold mb-4">Base reset (global)</h3>
        <form class="space-y-4">
          <div>
            <label for="twfp-emailA" class="mb-1 block text-sm font-medium"
             ...

CSS

/* Scoped styles so nothing leaks outside this section */
#tw-forms-playground {
  color-scheme: light dark;
}

/* --- Simulated @tailwindcss/forms — BASE STRATEGY (applies when wrapper has .forms-base) --- */
#tw-forms-playground.forms-base input[type="text"],
#tw-forms-playground.forms-base input[type="email"],
#tw-forms-playground.forms-base input[type="password"],
#tw-forms-playground.forms-base input[type="number"],
#tw-forms-playground.forms-base input[type="date"],
#tw-forms-playground.forms-base input[type="time"],
#tw-forms-playground.forms-base input[type="url"],
#tw-forms-playground.forms-base input[type="search"],
#tw-forms-playground.forms-base textarea,
#tw-forms-playground.forms-base select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #fff;
  color: #111827; /* gray-900 */
  border: 1px solid #d1d5db; /* gray-300 */
  border-radius: 0.375rem; /* rounded-md */
  padding: 0.5rem 0.75rem; /* px-3 py-2 */
  line-height: 1.5;
  outline: none;
}
#tw-forms-playground.forms-base select {
  background-image: none;
}
#tw-forms-playground.forms-base input:focus,
#tw-forms-playground.forms-base textarea:focus,
#tw-forms-playground.forms-base select:focus {
  border-color: #6366f1; /* indigo-500 */
  box-shadow: 0 0 0 2px rgba(199, 210, 254, 0.8); /* indigo-200 */
}
#tw-forms-playground.forms-base input[type="checkbox"],
#tw-forms-playground.forms-base input[type="radio"] {
  width: 1rem;
  height: 1rem;
  border-radius: 0.25rem;
  border: 1px solid #d1d5db;
  accent-color: #6366f1; /* indigo-500 */
}
#tw-forms-playground.forms-base input[type="radio"] {
  border-radius: 9999px;
}
#tw-forms-playground.forms-base input[type="file"]::file-selector-button {
  margin-right: 0.75rem;
  border: 1px solid #d1d5db;
  background: #f9fafb;
  padding: 0.5rem 0.75rem;
  border-radius: 0.375rem;
  cursor: pointer;
}

/* --- CLASS STRATEGY (opt-in via .form-*) ---...

JavaScript

document.addEventListener('DOMContentLoaded', () => {
  const root = document.getElementById('tw-forms-playground');
  const modeToggle = root.querySelector('#twfp-modeToggle');
  const darkToggle = root.querySelector('#twfp-darkToggle');
  const playground = root.querySelector('#twfp-playground');
  const panelBase = root.querySelector('#twfp-panelBase');
  const panelClass = root.querySelector('#twfp-panelClass');
  const copyBtn = root.querySelector('#twfp-copyBtn');

  // Toggle strategy
  modeToggle.addEventListener('change', () => {
    const useClass = modeToggle.checked;
    panelBase.classList.toggle('hidden', useClass);
    panelClass.classList.toggle('hidden', !useClass);
    playground.classList.toggle('forms-base', !useClass);
  });

  // Scoped dark mode (on wrapper only)
  darkToggle.addEventListener('change', () => {
    root.classList.toggle('dark', darkToggle.checked);
  });

  const sample = `<form class="space-y-4">
  <div>
    <label for="email" class="mb-1 block text-sm font-medium">Email</label>
    <input id="email" type="email" class="form-input block w-full" placeholder="you@example.com" />
  </div>
  <div>
    <label for="country" class="mb-1 block text-sm font-medium">Country</label>
    <select id="country" class="form-select block w-full">
      <option>Poland</option><option>Germany</option><option>Turkey</option>
    </select>
  </div>
  <fieldset class="space-y-2">
    <legend class="text-sm font-medium">Preferences</legend>
    <label cl