---
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: Angular
source: https://surveyjs.io/form-library/examples/tailwind-theme-adapter/angular
index: https://surveyjs.io/form-library/examples/overview.md
---

# Tailwind CSS Theme Adapter (Angular)

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

### `src/app/components/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/app/components/survey.component.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/app/components/survey.component.html`

```html
<survey [model]="model"></survey>
```

### `src/app/components/native-tailwind-form.component.html`

```html
<form
  #preferencesForm="ngForm"
  ngNativeValidate
  (ngSubmit)="save(preferencesForm)"
  (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"
      name="fullName"
      [(ngModel)]="preferences.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"
      name="email"
      [(ngModel)]="preferences.email"
      type="email"
      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"
      name="role"
      [(ngModel)]="preferences.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
          name="contactMethod"
          [(ngModel)]="preferences.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
          name="contactMethod"
          [(ngModel)]="preferences.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
            name="updates"
            type="checkbox"
            value="productNews"
            [ngModel]="preferences.updates.indexOf('productNews') !== -1"
            (ngModelChange)="setUpdate('productNews', $event)"
            [ngModelOptions]="{ standalone: true }"
            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
            name="updates"
            type="checkbox"
            value="tutorials"
            [ngModel]="preferences.updates.indexOf('tutorials') !== -1"
            (ngModelChange)="setUpdate('tutorials', $event)"
            [ngModelOptions]="{ standalone: true }"
            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"
      name="notes"
      [(ngModel)]="preferences.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>
```

### `src/app/components/native-tailwind-form.component.ts`

```ts
import { Component } from "@angular/core";
import { NgForm } from "@angular/forms";

@Component({
    selector: "native-tailwind-form",
    templateUrl: "./native-tailwind-form.component.html",
    styles: [":host { display: block; }"]
})
export class NativeTailwindFormComponent {
    preferences = {
        fullName: "",
        email: "",
        role: "",
        contactMethod: "email",
        updates: [] as string[],
        notes: ""
    };
    saved = false;

    setUpdate(value: string, checked: boolean): void {
        this.preferences.updates = checked
            ? [...this.preferences.updates, value]
            : this.preferences.updates.filter(item => item !== value);
    }

    save(form: NgForm): void {
        if (form.valid) this.saved = true;
    }
}
```

### `src/app/components/theme-controls.component.ts`

```ts
import { Component } from "@angular/core";
import { createThemeControls } from "./tailwind-demo";

@Component({
    selector: "theme-controls",
    template: "<survey [model]='model'></survey>",
    styles: [":host { display: block; }"]
})
export class ThemeControlsComponent {
    model = createThemeControls();
}
```

### `src/app/components/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/app/components/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/components/survey.component.ts`

```ts
import { Component, OnInit } from "@angular/core";
import { Model } from "survey-core";
import { json } from "./json";
import "./survey.component.css";
import "survey-core/survey-core.min.css";
import "survey-core/themes/adapters/tailwind4.css";
import "./tailwind.css";
import { createThemeControls } from "./tailwind-demo";

@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey",
    templateUrl: "./survey.component.html",
    styleUrls: ["./survey.component.css"]
})
export class SurveyComponent implements OnInit {
    model: Model;
    ngOnInit() {
        const survey = new Model(json);
        survey.onCompleting.add((_, options) => {
            options.allow = false;
            survey.notify("Preferences saved for this preview.", "success");
        });
        survey.onComplete.add((sender, options) => {
            console.log(JSON.stringify(sender.data, null, 3));
        });
        this.model = survey;
    }
}
```

### `src/app/app.component.css`

```css

```

### `src/app/app.component.html`

```html
<component-survey></component-survey>
```

### `src/app/app.component.ts`

```ts
import { Component } from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["./app.component.css"]
})
export class AppComponent {
    title = "CodeSandbox";
}
```

### `src/app/app.module.ts`

```ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { SurveyModule } from "survey-angular-ui";
import { AppComponent } from "./app.component";
import { SurveyComponent } from "./components/survey.component";
import { NativeTailwindFormComponent } from "./components/native-tailwind-form.component";
import { ThemeControlsComponent } from "./components/theme-controls.component";

@NgModule({
    declarations: [AppComponent, SurveyComponent, NativeTailwindFormComponent, ThemeControlsComponent],
    imports: [BrowserModule, FormsModule, SurveyModule],
    providers: [],
    bootstrap: [AppComponent, NativeTailwindFormComponent, ThemeControlsComponent]
})
export class AppModule { }
```

### `src/environments/environment.prod.ts`

```ts
export const environment = {
    production: true
};
```

### `src/environments/environment.ts`

```ts
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
    production: false
};
```

### `src/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"
        >
          <theme-controls id="themeControls"></theme-controls>
        </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>
            <native-tailwind-form id="nativeForm"></native-tailwind-form>
          </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>
            <app-root></app-root>
          </section>
        </div>
      </div>
    </div>
  </body>
</html>
```

### `src/main.ts`

```ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
```

### `src/polyfills.ts`

```ts
/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import "core-js/proposals/reflect-metadata";

/**
 * Required to support Web Animations `@angular/platform-browser/animations`.
 * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
 **/
// import 'web-animations-js';  // Run `npm install web-animations-js`.

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import "zone.js/dist/zone"; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
```

### `src/styles.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/typings.d.ts`

```ts
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
    id: string;
}
```

### `.angular-cli.json`

```json
{
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [ "assets", "favicon.ico" ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "prefix": "app",
      "styles": [ "styles.css" ],
      "scripts": [ ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ]
}
```

### `_tsconfig.json`

```json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "resolveJsonModule": true,
    "target": "es2015",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
```

### `package.json`

```json
{
  "name": "surveyjs-angular",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "14.1.1",
    "@angular/cdk": "14.1.1",
    "@angular/common": "14.1.1",
    "@angular/compiler": "14.1.1",
    "@angular/core": "14.1.1",
    "@angular/forms": "14.1.1",
    "@angular/platform-browser": "14.1.1",
    "@angular/platform-browser-dynamic": "14.1.1",
    "@angular/router": "14.1.1",
    "core-js": "3.6.4",
    "rxjs": "6.5.4",
    "survey-core": "latest",
    "survey-angular-ui": "latest",
    "tslib": "1.13.0",
    "zone.js": "0.11.7"
  },
  "devDependencies": {
    "@angular/cli": "1.6.6",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/core-js": "0.9.46",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "3.9.7"
  },
  "keywords": [ "angular", "surveyjs" ],
  "description": "SurveyJS-Angular example project"
}
```

## Other Frameworks

- [React](https://surveyjs.io/form-library/examples/tailwind-theme-adapter/reactjs.md)
- [Vue 3](https://surveyjs.io/form-library/examples/tailwind-theme-adapter/vue3js.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)
