Tailwind CSS

Tailwind CSS#

Overview#

Duxt includes first-class Tailwind CSS integration out of the box. New projects come pre-configured with Tailwind, so you can start using utility classes immediately.

Project Setup#

When you run duxt create, Tailwind is automatically configured:

my-app/
├── web/
│   ├── index.html
│   ├── styles.tw.css      # Tailwind input file
│   └── styles.css         # Generated CSS (don't edit)
├── tailwind.config.js
└── package.json

Tailwind Input File#

The web/styles.tw.css file is your Tailwind source:

@tailwind base;
@tailwind components;
@tailwind utilities;

/* Custom styles below */

Configuration#

tailwind.config.js#

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './lib/**/*.dart',
    './web/**/*.html',
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
        },
      },
    },
  },
  plugins: [],
}

Content Paths#

Tailwind scans your Dart files for class names:

content: [
  './lib/**/*.dart',      // All Dart components
  './web/**/*.html',      // HTML templates
],

Using Tailwind#

In Components#

Use the classes parameter on any element:

import 'package:jaspr/jaspr.dart';

class MyComponent extends StatelessComponent {
  @override
  Component build(BuildContext context) {
    return div(
      classes: 'flex items-center justify-between p-4 bg-white rounded-lg shadow',
      [
        h1(classes: 'text-2xl font-bold text-gray-900', [
          text('Hello World'),
        ]),
        button(
          classes: 'px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600',
          [text('Click me')],
        ),
      ],
    );
  }
}

Responsive Design#

Use Tailwind's responsive prefixes:

div(
  classes: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4',
  [
    // Cards
  ],
)

Dark Mode#

Enable dark mode in tailwind.config.js:

module.exports = {
  darkMode: 'class',  // or 'media'
  // ...
}

Use in components:

div(
  classes: 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white',
  [text('Supports dark mode')],
)

Hover, Focus, and States#

button(
  classes: '''
    px-4 py-2 bg-blue-500 text-white rounded
    hover:bg-blue-600
    focus:outline-none focus:ring-2 focus:ring-blue-500
    active:bg-blue-700
    disabled:opacity-50 disabled:cursor-not-allowed
  ''',
  [text('Interactive button')],
)

Development Workflow#

Watch Mode#

During development, duxt dev automatically rebuilds Tailwind when you save files:

duxt dev

The dev server runs Tailwind in watch mode, so changes appear instantly.

Manual Build#

To build Tailwind manually:

npx tailwindcss -i web/styles.tw.css -o web/styles.css --watch

For production (minified):

npx tailwindcss -i web/styles.tw.css -o web/styles.css --minify

Adding Tailwind to Existing Project#

If your project doesn't have Tailwind configured:

1. Install Dependencies#

npm init -y
npm install -D tailwindcss
npx tailwindcss init

2. Create Input File#

Create web/styles.tw.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

3. Configure tailwind.config.js#

module.exports = {
  content: ['./lib/**/*.dart', './web/**/*.html'],
  theme: { extend: {} },
  plugins: [],
}

4. Add to index.html#

<link rel="stylesheet" href="styles.css">

5. Build#

npx tailwindcss -i web/styles.tw.css -o web/styles.css

Custom Components#

@apply Directive#

Create reusable styles in styles.tw.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn {
    @apply px-4 py-2 rounded font-medium transition-colors;
  }

  .btn-primary {
    @apply btn bg-blue-500 text-white hover:bg-blue-600;
  }

  .btn-secondary {
    @apply btn bg-gray-200 text-gray-800 hover:bg-gray-300;
  }

  .card {
    @apply bg-white rounded-lg shadow-md p-6;
  }

  .input {
    @apply w-full px-3 py-2 border border-gray-300 rounded-md
           focus:outline-none focus:ring-2 focus:ring-blue-500;
  }
}

Use in Dart:

button(classes: 'btn-primary', [text('Save')])
button(classes: 'btn-secondary', [text('Cancel')])

Tailwind Plugins#

Typography#

For markdown/prose content:

npm install -D @tailwindcss/typography
// tailwind.config.js
module.exports = {
  plugins: [
    require('@tailwindcss/typography'),
  ],
}
div(classes: 'prose prose-lg', [
  // Rendered markdown content
])

Forms#

For better form styling:

npm install -D @tailwindcss/forms
plugins: [
  require('@tailwindcss/forms'),
],

Container Queries#

For component-based responsive design:

npm install -D @tailwindcss/container-queries

With Duxt UI#

Duxt UI components are built with Tailwind and work seamlessly:

import 'package:duxt_ui/duxt_ui.dart';

// Duxt UI components use Tailwind internally
DButton(
  label: 'Click me',
  color: DButtonColor.primary,
)

// You can still add custom Tailwind classes
div(
  classes: 'mt-4 p-4 bg-gray-100 rounded',
  [
    DCard(
      title: 'My Card',
      description: 'Card content',
    ),
  ],
)

Tips#

String Interpolation#

Build dynamic classes:

final isActive = true;
final size = 'lg';

div(
  classes: 'p-4 rounded ${isActive ? 'bg-blue-500' : 'bg-gray-200'}',
  [text('Dynamic styling')],
)

Conditional Classes#

String buttonClasses(bool disabled, bool loading) {
  return [
    'px-4 py-2 rounded',
    if (disabled) 'opacity-50 cursor-not-allowed',
    if (loading) 'animate-pulse',
    if (!disabled && !loading) 'hover:bg-blue-600',
  ].join(' ');
}

Class Utilities#

Create a helper for complex class combinations:

String cn(List<String?> classes) {
  return classes.whereType<String>().join(' ');
}

// Usage
div(
  classes: cn([
    'flex items-center',
    isLarge ? 'p-8' : 'p-4',
    hasError ? 'border-red-500' : null,
  ]),
  [...],
)