---
title: Tailwind CSS Theme Adapter
product: Form Library
description: Compare a Tailwind CSS HTML form with a SurveyJS form. Change the primary color, light or dark mode, and corner radius to see both forms update together.
framework: Vue 3
source: https://surveyjs.io/form-library/examples/tailwind-theme-adapter/vue3js
index: https://surveyjs.io/form-library/examples/overview.md
---

# Tailwind CSS Theme Adapter (Vue 3)

This demo compares a native Tailwind CSS form (on the left) with a JSON-defined SurveyJS form styled using the Tailwind CSS v4 theme adapter (on the right). Change the primary color, light/dark appearance, and corner radius above the forms to see both update together. Open the **Code** tab to compare their implementations.

[Tailwind CSS v4 Theme Adapter Documentation](/form-library/documentation/theme-adapters#tailwind-css-v4 (linkStyle))

[Healthcare Demo Built with the Tailwind Adapter](https://healthcare.demos.surveyjs.io/ (linkStyle))

## Files

### `public/index.html`

```html
<link href="https://fonts.googleapis.com/css?family=Open%20Sans:400,600&display=swap" rel="stylesheet">
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Tailwind CSS Theme Adapter</title>
  </head>
  <body>
    <div
      id="tailwind-demo"
      class="tailwind-demo bg-background p-4 font-sans text-foreground sm:p-6"
      data-mode="light"
      data-radius="rounded"
    >
      <div class="mx-auto max-w-6xl">
        <section
          class="theme-controls mb-6 flex flex-col rounded-lg border border-border bg-card p-4"
          aria-label="Customize both forms"
        >
          <div id="themeControls"></div>
        </section>
        <div class="grid items-start gap-6 md:grid-cols-2">
          <section
            class="native-card min-w-0 rounded-lg border border-border bg-card p-4 shadow-sm"
            aria-labelledby="tailwind-native-heading"
          >
            <header class="mb-5">
              <p class="mb-3 text-xs font-semibold uppercase tracking-wide text-primary">
                HTML + Tailwind CSS
              </p>
              <h2 id="tailwind-native-heading" class="text-lg font-semibold leading-7">
                Contact preferences
              </h2>
              <p class="mt-1 text-sm leading-5 text-muted-foreground">
                Keep your profile and updates in sync.
              </p>
            </header>
            <div id="nativeForm"></div>
          </section>
          <section
            class="survey-card flex min-w-0 flex-col rounded-lg border border-border bg-card p-4 shadow-sm"
            aria-labelledby="tailwind-survey-heading"
          >
            <header class="mb-5">
              <p class="mb-3 text-xs font-semibold uppercase tracking-wide text-primary">
                SurveyJS + theme adapter
              </p>
              <h2 id="tailwind-survey-heading" class="text-lg font-semibold leading-7">
                Contact preferences
              </h2>
              <p class="mt-1 text-sm leading-5 text-muted-foreground">
                Keep your profile and updates in sync.
              </p>
            </header>
            <div id="app"></div>
          </section>
        </div>
      </div>
    </div>
  </body>
</html>
```

### `src/NativeTailwindForm.vue`

```html
<template>
  <form
    @submit.prevent="saved = true"
    @input="saved = false"
    data-native-form
    class="native-form flex flex-col gap-4 pt-4"
  >
    <div class="flex flex-col gap-1.5">
      <label for="tailwind-full-name" class="text-sm font-medium leading-5">
        Full name
        <span class="text-destructive" aria-hidden="true">*</span>
      </label>
      <input
        id="tailwind-full-name"
        v-model="form.fullName"
        name="fullName"
        type="text"
        autocomplete="name"
        required
        placeholder="Alex Morgan"
        class="h-9 w-full rounded-md border border-input bg-card px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
      />
    </div>
    <div class="flex flex-col gap-1.5">
      <label for="tailwind-email" class="text-sm font-medium leading-5">
        Email address
        <span class="text-destructive" aria-hidden="true">*</span>
      </label>
      <input
        id="tailwind-email"
        v-model="form.email"
        name="email"
        type="email"
        autocomplete="email"
        required
        placeholder="alex@example.com"
        class="h-9 w-full rounded-md border border-input bg-card px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
      />
    </div>
    <div class="flex flex-col gap-1.5">
      <label for="tailwind-role" class="text-sm font-medium leading-5">Your role</label>
      <select
        id="tailwind-role"
        v-model="form.role"
        name="role"
        class="h-9 w-full rounded-md border border-input bg-card px-3 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
      >
        <option value="">Select your role</option>
        <option value="designer">Designer</option>
        <option value="developer">Developer</option>
        <option value="productManager">Product manager</option>
      </select>
    </div>
    <fieldset class="min-w-0">
      <legend class="mb-1.5 text-sm font-medium leading-5">Preferred contact method</legend>
      <div class="flex flex-col gap-7 py-3">
        <label class="flex cursor-pointer items-center gap-2 text-sm leading-5">
          <input
            v-model="form.contactMethod"
            name="contactMethod"
            type="radio"
            value="email"
            class="-my-0.5 h-6 w-6 shrink-0 appearance-none rounded-full border border-input bg-card checked:border-[9px] checked:border-primary checked:bg-primary-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
          />
          Email
        </label>
        <label class="flex cursor-pointer items-center gap-2 text-sm leading-5">
          <input
            v-model="form.contactMethod"
            name="contactMethod"
            type="radio"
            value="phone"
            class="-my-0.5 h-6 w-6 shrink-0 appearance-none rounded-full border border-input bg-card checked:border-[9px] checked:border-primary checked:bg-primary-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
          />
          Phone
        </label>
      </div>
    </fieldset>
    <fieldset class="min-w-0">
      <legend class="mb-1.5 text-sm font-medium leading-5">Send me updates about</legend>
      <div class="flex flex-col gap-7 py-3">
        <label class="flex cursor-pointer items-center gap-2 text-sm leading-5">
          <span class="relative -my-0.5 inline-flex h-6 w-6 shrink-0">
            <input
              v-model="form.updates"
              name="updates"
              type="checkbox"
              value="productNews"
              class="peer h-6 w-6 appearance-none rounded-sm border border-input bg-card checked:border-primary checked:bg-primary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
            />
            <svg
              class="pointer-events-none absolute inset-1 h-4 w-4 text-primary-foreground opacity-0 peer-checked:opacity-100"
              viewBox="0 0 16 16"
              fill="none"
              aria-hidden="true"
            >
              <path
                d="m4 8 2.5 2.5L12 5"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              />
            </svg>
          </span>
          Product news
        </label>
        <label class="flex cursor-pointer items-center gap-2 text-sm leading-5">
          <span class="relative -my-0.5 inline-flex h-6 w-6 shrink-0">
            <input
              v-model="form.updates"
              name="updates"
              type="checkbox"
              value="tutorials"
              class="peer h-6 w-6 appearance-none rounded-sm border border-input bg-card checked:border-primary checked:bg-primary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
            />
            <svg
              class="pointer-events-none absolute inset-1 h-4 w-4 text-primary-foreground opacity-0 peer-checked:opacity-100"
              viewBox="0 0 16 16"
              fill="none"
              aria-hidden="true"
            >
              <path
                d="m4 8 2.5 2.5L12 5"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              />
            </svg>
          </span>
          Tips and tutorials
        </label>
      </div>
    </fieldset>
    <div class="flex flex-col gap-1.5">
      <label for="tailwind-notes" class="text-sm font-medium leading-5">Notes</label>
      <textarea
        id="tailwind-notes"
        v-model="form.notes"
        name="notes"
        rows="3"
        placeholder="Anything else we should know?"
        class="h-19 w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm leading-5 text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
      ></textarea>
    </div>
    <button
      type="submit"
      class="mt-4 h-13 w-full cursor-pointer rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-card"
    >
      Save preferences
    </button>
    <output data-native-status role="status" :hidden="!saved" class="text-sm text-muted-foreground">
      Preferences saved for this preview.
    </output>
  </form>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";

const form = reactive({
  fullName: "",
  email: "",
  role: "",
  contactMethod: "email",
  updates: [],
  notes: ""
});
const saved = ref(false);

</script>
```

### `src/tailwind-demo.js`

```js
import { Model } from "survey-core";

const themeControlsJson = {
    widthMode: "responsive",
    showNavigationButtons: false,
    questionTitleLocation: "left",
    pages: [{
        name: "themeSettings",
        questionTitleWidth: "auto",
        elements: [{
            type: "dropdown",
            name: "primary",
            title: "Primary color:",
            defaultValue: "indigo",
            allowClear: false,
            searchEnabled: false,
            width: "250px",
            minWidth: "230px",
            choices: [
                { value: "indigo", text: "Indigo" },
                { value: "blue", text: "Blue" },
                { value: "emerald", text: "Emerald" },
                { value: "rose", text: "Rose" }
            ]
        }, {
            type: "dropdown",
            name: "mode",
            title: "Appearance:",
            defaultValue: "light",
            allowClear: false,
            searchEnabled: false,
            startWithNewLine: false,
            width: "220px",
            minWidth: "200px",
            choices: [
                { value: "light", text: "Light" },
                { value: "dark", text: "Dark" }
            ]
        }, {
            type: "dropdown",
            name: "radius",
            title: "Corner radius:",
            defaultValue: "rounded",
            allowClear: false,
            searchEnabled: false,
            startWithNewLine: false,
            width: "240px",
            minWidth: "220px",
            choices: [
                { value: "square", text: "Square" },
                { value: "rounded", text: "Rounded" },
                { value: "soft", text: "Soft" }
            ]
        }]
    }]
};

export function createThemeControls() {
    const controls = new Model(themeControlsJson);
    const demo = document.getElementById("tailwind-demo");
    const colors = {
        indigo: "#4f46e5",
        blue: "#2563eb",
        emerald: "#047857",
        rose: "#be123c"
    };

    function updateTheme() {
        const color = colors[controls.getValue("primary")];
        if (!color) return;
        demo.style.setProperty("--color-primary", color);
        demo.style.setProperty("--color-ring", color);
        demo.dataset.mode = controls.getValue("mode");
        demo.dataset.radius = controls.getValue("radius");
    }

    controls.onValueChanged.add(updateTheme);
    controls.onCompleting.add((_, options) => { options.allow = false; });
    updateTheme();
    return controls;
}

export function initializeNativeTailwindForm() {
    const form = document.querySelector("#nativeForm form");
    const status = form.querySelector("[data-native-status]");

    form.addEventListener("submit", event => {
        event.preventDefault();
        status.textContent = "Preferences saved for this preview.";
        status.hidden = false;
    });
    form.addEventListener("input", () => { status.hidden = true; });
}
```

### `src/tailwind.css`

```css
/* Precompiled Tailwind CSS utilities for the comparison page and native form components. */
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
  :root, :host {
    --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
      "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
      "Courier New", monospace;
    --spacing: 0.25rem;
    --container-6xl: 72rem;
    --text-xs: 0.75rem;
    --text-xs--line-height: calc(1 / 0.75);
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --text-lg: 1.125rem;
    --text-lg--line-height: calc(1.75 / 1.125);
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --tracking-wide: 0.025em;
    --radius-sm: 0.25rem;
    --radius-md: 0.375rem;
    --radius-lg: 0.5rem;
    --default-font-family: var(--font-sans);
    --default-mono-font-family: var(--font-mono);
    --color-background: #f9fafb;
    --color-primary: #4f46e5;
    --color-primary-hover: color-mix(in srgb, #4f46e5 85%, black);
    @supports (color: color-mix(in lab, red, red)) {
      --color-primary-hover: color-mix(in srgb, var(--color-primary) 85%, black);
    }
    --color-primary-foreground: #ffffff;
    --color-ring: var(--color-primary);
    --color-card: #ffffff;
    --color-foreground: #111827;
    --color-muted-foreground: #6b7280;
    --color-input: #d1d5db;
    --color-border: #e5e7eb;
    --color-destructive: #dc2626;
  }
}
@layer base {
  *, ::after, ::before, ::backdrop, ::file-selector-button {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: 0 solid;
  }
  html, :host {
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
    font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
    font-feature-settings: var(--default-font-feature-settings, normal);
    font-variation-settings: var(--default-font-variation-settings, normal);
    -webkit-tap-highlight-color: transparent;
  }
  hr {
    height: 0;
    color: inherit;
    border-top-width: 1px;
  }
  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }
  h1, h2, h3, h4, h5, h6 {
    font-size: inherit;
    font-weight: inherit;
  }
  a {
    color: inherit;
    -webkit-text-decoration: inherit;
    text-decoration: inherit;
  }
  b, strong {
    font-weight: bolder;
  }
  code, kbd, samp, pre {
    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
    font-feature-settings: var(--default-mono-font-feature-settings, normal);
    font-variation-settings: var(--default-mono-font-variation-settings, normal);
    font-size: 1em;
  }
  small {
    font-size: 80%;
  }
  sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }
  sub {
    bottom: -0.25em;
  }
  sup {
    top: -0.5em;
  }
  table {
    text-indent: 0;
    border-color: inherit;
    border-collapse: collapse;
  }
  :-moz-focusring {
    outline: auto;
  }
  progress {
    vertical-align: baseline;
  }
  summary {
    display: list-item;
  }
  ol, ul, menu {
    list-style: none;
  }
  img, svg, video, canvas, audio, iframe, embed, object {
    display: block;
    vertical-align: middle;
  }
  img, video {
    max-width: 100%;
    height: auto;
  }
  button, input, select, optgroup, textarea, ::file-selector-button {
    font: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    letter-spacing: inherit;
    color: inherit;
    border-radius: 0;
    background-color: transparent;
    opacity: 1;
  }
  :where(select:is([multiple], [size])) optgroup {
    font-weight: bolder;
  }
  :where(select:is([multiple], [size])) optgroup option {
    padding-inline-start: 20px;
  }
  ::file-selector-button {
    margin-inline-end: 4px;
  }
  ::placeholder {
    opacity: 1;
  }
  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {
    ::placeholder {
      color: currentcolor;
      @supports (color: color-mix(in lab, red, red)) {
        color: color-mix(in oklab, currentcolor 50%, transparent);
      }
    }
  }
  textarea {
    resize: vertical;
  }
  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }
  ::-webkit-date-and-time-value {
    min-height: 1lh;
    text-align: inherit;
  }
  ::-webkit-datetime-edit {
    display: inline-flex;
  }
  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }
  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
    padding-block: 0;
  }
  ::-webkit-calendar-picker-indicator {
    line-height: 1;
  }
  :-moz-ui-invalid {
    box-shadow: none;
  }
  button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
    appearance: button;
  }
  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
    height: auto;
  }
  [hidden]:where(:not([hidden="until-found"])) {
    display: none !important;
  }
}
@layer utilities {
  .pointer-events-none {
    pointer-events: none;
  }
  .absolute {
    position: absolute;
  }
  .relative {
    position: relative;
  }
  .inset-1 {
    inset: var(--spacing);
  }
  .mx-auto {
    margin-inline: auto;
  }
  .-my-0\.5 {
    margin-block: calc(var(--spacing) * -0.5);
  }
  .mt-1 {
    margin-top: var(--spacing);
  }
  .mt-4 {
    margin-top: calc(var(--spacing) * 4);
  }
  .mb-1\.5 {
    margin-bottom: calc(var(--spacing) * 1.5);
  }
  .mb-3 {
    margin-bottom: calc(var(--spacing) * 3);
  }
  .mb-5 {
    margin-bottom: calc(var(--spacing) * 5);
  }
  .mb-6 {
    margin-bottom: calc(var(--spacing) * 6);
  }
  .flex {
    display: flex;
  }
  .grid {
    display: grid;
  }
  .inline-flex {
    display: inline-flex;
  }
  .h-4 {
    height: calc(var(--spacing) * 4);
  }
  .h-6 {
    height: calc(var(--spacing) * 6);
  }
  .h-9 {
    height: calc(var(--spacing) * 9);
  }
  .h-13 {
    height: calc(var(--spacing) * 13);
  }
  .h-19 {
    height: calc(var(--spacing) * 19);
  }
  .w-4 {
    width: calc(var(--spacing) * 4);
  }
  .w-6 {
    width: calc(var(--spacing) * 6);
  }
  .w-full {
    width: 100%;
  }
  .max-w-6xl {
    max-width: var(--container-6xl);
  }
  .min-w-0 {
    min-width: 0;
  }
  .shrink-0 {
    flex-shrink: 0;
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .resize-y {
    resize: vertical;
  }
  .appearance-none {
    appearance: none;
  }
  .flex-col {
    flex-direction: column;
  }
  .items-center {
    align-items: center;
  }
  .items-start {
    align-items: flex-start;
  }
  .gap-1\.5 {
    gap: calc(var(--spacing) * 1.5);
  }
  .gap-2 {
    gap: calc(var(--spacing) * 2);
  }
  .gap-4 {
    gap: calc(var(--spacing) * 4);
  }
  .gap-6 {
    gap: calc(var(--spacing) * 6);
  }
  .gap-7 {
    gap: calc(var(--spacing) * 7);
  }
  .rounded-full {
    border-radius: calc(infinity * 1px);
  }
  .rounded-lg {
    border-radius: var(--radius-lg);
  }
  .rounded-md {
    border-radius: var(--radius-md);
  }
  .rounded-sm {
    border-radius: var(--radius-sm);
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-border {
    border-color: var(--color-border);
  }
  .border-input {
    border-color: var(--color-input);
  }
  .bg-background {
    background-color: var(--color-background);
  }
  .bg-card {
    background-color: var(--color-card);
  }
  .bg-primary {
    background-color: var(--color-primary);
  }
  .p-4 {
    padding: calc(var(--spacing) * 4);
  }
  .px-3 {
    padding-inline: calc(var(--spacing) * 3);
  }
  .px-4 {
    padding-inline: calc(var(--spacing) * 4);
  }
  .py-2 {
    padding-block: calc(var(--spacing) * 2);
  }
  .py-3 {
    padding-block: calc(var(--spacing) * 3);
  }
  .pt-4 {
    padding-top: calc(var(--spacing) * 4);
  }
  .font-sans {
    font-family: var(--font-sans);
  }
  .text-lg {
    font-size: var(--text-lg);
    line-height: var(--tw-leading, var(--text-lg--line-height));
  }
  .text-sm {
    font-size: var(--text-sm);
    line-height: var(--tw-leading, var(--text-sm--line-height));
  }
  .text-xs {
    font-size: var(--text-xs);
    line-height: var(--tw-leading, var(--text-xs--line-height));
  }
  .leading-5 {
    --tw-leading: calc(var(--spacing) * 5);
    line-height: calc(var(--spacing) * 5);
  }
  .leading-7 {
    --tw-leading: calc(var(--spacing) * 7);
    line-height: calc(var(--spacing) * 7);
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .font-semibold {
    --tw-font-weight: var(--font-weight-semibold);
    font-weight: var(--font-weight-semibold);
  }
  .tracking-wide {
    --tw-tracking: var(--tracking-wide);
    letter-spacing: var(--tracking-wide);
  }
  .text-destructive {
    color: var(--color-destructive);
  }
  .text-foreground {
    color: var(--color-foreground);
  }
  .text-muted-foreground {
    color: var(--color-muted-foreground);
  }
  .text-primary {
    color: var(--color-primary);
  }
  .text-primary-foreground {
    color: var(--color-primary-foreground);
  }
  .uppercase {
    text-transform: uppercase;
  }
  .opacity-0 {
    opacity: 0%;
  }
  .shadow-sm {
    --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .peer-checked\:opacity-100 {
    &:is(:where(.peer):checked ~ *) {
      opacity: 100%;
    }
  }
  .placeholder\:text-muted-foreground {
    &::placeholder {
      color: var(--color-muted-foreground);
    }
  }
  .checked\:border-\[9px\] {
    &:checked {
      border-style: var(--tw-border-style);
      border-width: 9px;
    }
  }
  .checked\:border-primary {
    &:checked {
      border-color: var(--color-primary);
    }
  }
  .checked\:bg-primary {
    &:checked {
      background-color: var(--color-primary);
    }
  }
  .checked\:bg-primary-foreground {
    &:checked {
      background-color: var(--color-primary-foreground);
    }
  }
  .hover\:bg-primary-hover {
    &:hover {
      @media (hover: hover) {
        background-color: var(--color-primary-hover);
      }
    }
  }
  .focus\:ring-2 {
    &:focus {
      --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
      box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
    }
  }
  .focus\:ring-ring {
    &:focus {
      --tw-ring-color: var(--color-ring);
    }
  }
  .focus\:ring-offset-2 {
    &:focus {
      --tw-ring-offset-width: 2px;
      --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
    }
  }
  .focus\:ring-offset-card {
    &:focus {
      --tw-ring-offset-color: var(--color-card);
    }
  }
  .focus\:outline-none {
    &:focus {
      --tw-outline-style: none;
      outline-style: none;
    }
  }
  .sm\:p-6 {
    @media (width >= 40rem) {
      padding: calc(var(--spacing) * 6);
    }
  }
  .md\:grid-cols-2 {
    @media (width >= 48rem) {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }
}
@property --tw-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@property --tw-leading {
  syntax: "*";
  inherits: false;
}
@property --tw-font-weight {
  syntax: "*";
  inherits: false;
}
@property --tw-tracking {
  syntax: "*";
  inherits: false;
}
@property --tw-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-inset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-inset-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-ring-color {
  syntax: "*";
  inherits: false;
}
@property --tw-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
  syntax: "*";
  inherits: false;
}
@property --tw-inset-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
  syntax: "*";
  inherits: false;
}
@property --tw-ring-offset-width {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --tw-ring-offset-color {
  syntax: "*";
  inherits: false;
  initial-value: #fff;
}
@property --tw-ring-offset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@layer properties {
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
    *, ::before, ::after, ::backdrop {
      --tw-border-style: solid;
      --tw-leading: initial;
      --tw-font-weight: initial;
      --tw-tracking: initial;
      --tw-shadow: 0 0 #0000;
      --tw-shadow-color: initial;
      --tw-shadow-alpha: 100%;
      --tw-inset-shadow: 0 0 #0000;
      --tw-inset-shadow-color: initial;
      --tw-inset-shadow-alpha: 100%;
      --tw-ring-color: initial;
      --tw-ring-shadow: 0 0 #0000;
      --tw-inset-ring-color: initial;
      --tw-inset-ring-shadow: 0 0 #0000;
      --tw-ring-inset: initial;
      --tw-ring-offset-width: 0px;
      --tw-ring-offset-color: #fff;
      --tw-ring-offset-shadow: 0 0 #0000;
    }
  }
}
```

### `src/App.vue`

```html
<script lang="ts">
import "survey-core/themes/adapters/tailwind4.css";
import "./tailwind.css";
import { createThemeControls } from "./tailwind-demo";

import { createApp } from "vue";
import { SurveyComponent } from "survey-vue3-ui";
import NativeTailwindForm from "./NativeTailwindForm.vue";

createApp(SurveyComponent, { model: createThemeControls() }).mount("#themeControls");
createApp(NativeTailwindForm).mount("#nativeForm");
</script>
<template>
    <SurveyComponent :model="survey" />
</template>
<script setup lang="ts">
    import { Model } from "survey-core";
    import { SurveyComponent } from "survey-vue3-ui";
    import "survey-core/survey-core.min.css";
    import "survey-core/themes/adapters/tailwind4.css";
    import "./tailwind.css";
    import { createThemeControls } from "./tailwind-demo";
    import "./index.css";
    import { json } from "./json";

    const survey = new Model(json);
    survey.onComplete.add((sender, options) => {
        console.log(JSON.stringify(sender.data, null, 3));
    });
    survey.onCompleting.add((_, options) => {
        options.allow = false;
        survey.notify("Preferences saved for this preview.", "success");
    });
</script>
```

### `src/index.css`

```css
/* Host theme tokens shared by Tailwind utilities and the SurveyJS adapter. */
.tailwind-demo {
    --spacing: 0.25rem;
    --font-sans: ui-sans-serif, system-ui, sans-serif;
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --color-primary: #4f46e5;
    --color-primary-foreground: #ffffff;
    --color-primary-hover: color-mix(in srgb, var(--color-primary) 85%, black);
    --color-ring: var(--color-primary);
    --color-background: #f9fafb;
    --color-card: #ffffff;
    --color-foreground: #111827;
    --color-muted-foreground: #6b7280;
    --color-input: #d1d5db;
    --color-border: #e5e7eb;
    --color-destructive: #dc2626;
    --radius-sm: 0.25rem;
    --radius-md: 0.375rem;
    --radius-lg: 0.5rem;
    box-sizing: border-box;
    min-height: 100vh;
    flex-shrink: 0;
    padding: 24px;
    background: var(--color-background);
    color: var(--color-foreground);
    color-scheme: light;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    line-height: 1.25rem;
}

.tailwind-demo[data-mode="dark"] {
    --color-background: #030712;
    --color-card: #111827;
    --color-foreground: #f9fafb;
    --color-muted-foreground: #9ca3af;
    --color-input: #4b5563;
    --color-border: #374151;
    --color-destructive: #f87171;
    color-scheme: dark;
}

.tailwind-demo[data-radius="square"] {
    --radius-sm: 0px;
    --radius-md: 0px;
    --radius-lg: 0px;
}

.tailwind-demo[data-radius="soft"] {
    --radius-sm: 0.375rem;
    --radius-md: 0.75rem;
    --radius-lg: 1rem;
}

.tailwind-demo *,
.tailwind-demo *::before,
.tailwind-demo *::after {
    box-sizing: border-box;
}

.tailwind-demo [hidden] {
    display: none;
}

/* Only host layout changes: component styling comes from tailwind4.css. */
.tailwind-demo .sjs-theme-overrides {
    --sjs-element-min-width: 0px;
    --sjs2-layout-component-page-content-area-padding-horizontal: 0px;
    --sjs2-layout-component-survey-footer-padding-horizontal: 0px;
    --sjs2-layout-component-survey-footer-padding-top: 16px;
    --sjs2-layout-component-survey-footer-padding-bottom: 0px;
}

.theme-controls .sjs-theme-overrides {
    --sjs2-layout-component-survey-box-gap-vertical: 0px;
    --sjs2-layout-component-question-box-gap-horizontal: 8px;
}

.tailwind-demo app-root,
.tailwind-demo component-survey,
.tailwind-demo theme-controls {
    display: block;
}

@media (max-width: 600px) {
    .tailwind-demo {
        padding: 16px;
    }
}
```

### `src/json.ts`

```ts
export const json = {
  "widthMode": "responsive",
  "completeText": "Save preferences",
  "pages": [
    {
      "name": "preferences",
      "elements": [
        {
          "type": "text",
          "name": "fullName",
          "title": "Full name",
          "isRequired": true,
          "autocomplete": "name",
          "placeholder": "Alex Morgan"
        },
        {
          "type": "text",
          "name": "email",
          "title": "Email address",
          "isRequired": true,
          "inputType": "email",
          "autocomplete": "email",
          "placeholder": "alex@example.com"
        },
        {
          "type": "dropdown",
          "name": "role",
          "title": "Your role",
          "placeholder": "Select your role",
          "choices": [
            { "value": "designer", "text": "Designer" },
            { "value": "developer", "text": "Developer" },
            { "value": "productManager", "text": "Product manager" }
          ]
        },
        {
          "type": "radiogroup",
          "name": "contactMethod",
          "title": "Preferred contact method",
          "defaultValue": "email",
          "choices": [
            { "value": "email", "text": "Email" },
            { "value": "phone", "text": "Phone" }
          ]
        },
        {
          "type": "checkbox",
          "name": "updates",
          "title": "Send me updates about",
          "choices": [
            { "value": "productNews", "text": "Product news" },
            { "value": "tutorials", "text": "Tips and tutorials" }
          ]
        },
        {
          "type": "comment",
          "name": "notes",
          "title": "Notes",
          "rows": 3,
          "placeholder": "Anything else we should know?"
        }
      ]
    }
  ]
};
```

### `src/main.ts`

```ts
import { createApp } from "vue";
import App from "./App.vue";

const app = createApp(App);
app.mount("#app");
```

### `src/shims-vue.d.ts`

```ts
/* eslint-disable */
declare module "*.vue" {
    import type { DefineComponent } from "vue"
    const component: DefineComponent<{}, {}, any>
    export default component
}
```

### `.babelrc`

```
{}
```

### `.eslintrc.js`

```js
module.exports = {
    root: true,
    env: {
        node: true
    },
    extends: [
        "plugin:vue/vue3-essential",
        "eslint:recommended",
        "@vue/typescript/recommended",
        "@vue/prettier",
        "@vue/prettier/@typescript-eslint"
    ],
    parserOptions: {
        ecmaVersion: 2020
    },
    rules: {
        "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
        "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
    },
    overrides: [
        {
            files: [
                "**/__tests__/*.{j,t}s?(x)",
                "**/tests/unit/**/*.spec.{j,t}s?(x)"
            ],
            env: {
                jest: true
            }
        }
    ]
};
```

### `babel.config.js`

```js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"]
};
```

### `package.json`

```json
{
  "name": "surveyjs-library-vue3",
  "version": "0.1.0",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "tslib": "2.6.1",
    "vue": "^3.4.1",
    "survey-core": "latest",
    "survey-vue3-ui": "latest",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^2.33.0",
    "@typescript-eslint/parser": "^2.33.0",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-pwa": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/eslint-config-typescript": "^5.0.2",
    "@vue/test-utils": "^2.0.0-0",
    "eslint": "^6.7.2",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-vue": "^7.0.0-0",
    "node-sass": "^4.12.0",
    "prettier": "^1.19.1",
    "sass-loader": "^8.0.2",
    "typescript": "~3.9.3"
  }
}
```

### `tsconfig.json`

```json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "jest"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

## Other Frameworks

- [Angular](https://surveyjs.io/form-library/examples/tailwind-theme-adapter/angular.md)
- [React](https://surveyjs.io/form-library/examples/tailwind-theme-adapter/reactjs.md)
- [jQuery](https://surveyjs.io/form-library/examples/tailwind-theme-adapter/jquery.md)
- [Vanilla JS](https://surveyjs.io/form-library/examples/tailwind-theme-adapter/vanillajs.md)
