Skip to content

Tailwind V3 Astro Setup

A streamlined project template for building high-performance websites with Astro, Tailwind CSS v3, and React support.

  • ⚡️ Astro - Blazing fast static site generator with Island Architecture
  • 💨 Tailwind CSS v3 - Utility-first CSS framework
  • ⚛️ React - Component-based UI library for dynamic features
  • 📝 Markdown Support - Write content in Markdown
  • 🧹 Linting - ESLint, Prettier, and Stylelint configuration
  • 🔍 TypeScript - Type checking and code intelligence
  • 📱 Responsive - Mobile-first design approach
  • 🚀 Optimized - Built-in performance optimization
  • Node.js 16.x or higher
  • npm or yarn
  • Clone this repository

    Terminal window
    git clone https://github.com/yourusername/astro-tailwind-template.git my-project
    cd my-project
  • Install dependencies

    Terminal window
    npm install
    # or
    yarn install
  • Start the development server

Terminal window
npm run dev
# or
yarn dev
  • Open your browser and visit http://localhost:3000
/
├── .github/ # GitHub configuration
├── public/ # Static assets
│ ├── favicon.svg
│ └── robots.txt
├── src/
│ ├── components/ # Reusable components
│ │ ├── astro/ # Astro components
│ │ └── react/ # React components
│ ├── layouts/ # Page layouts
│ ├── pages/ # Page routes and endpoints
│ │ ├── posts/ # Markdown blog posts
│ │ └── index.astro # Homepage
│ ├── styles/ # Global styles
│ └── utils/ # Utility functions
├── .eslintrc.json # ESLint configuration
├── .prettierrc.json # Prettier configuration
├── .stylelintrc.json # Stylelint configuration
├── astro.config.mjs # Astro configuration
├── tailwind.config.js # Tailwind CSS configuration
└── tsconfig.json # TypeScript configuration

The Astro configuration is in astro.config.mjs:

import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import react from '@astrojs/react';
export default defineConfig({
integrations: [tailwind(), react()],
markdown: {
shikiConfig: {
theme: 'github-dark',
wrap: true,
},
},
});

The Tailwind CSS configuration is in tailwind.config.js:

/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
fontFamily: {
sans: ['Roboto', 'sans-serif'],
mono: ['Roboto Mono', 'monospace'],
slab: ['Roboto Slab', 'serif'],
},
},
},
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
};

The ESLint configuration is in .eslintrc.json:

{
"env": {
"browser": true,
"es2021": true,
"es2022": true,
"es6": true,
"jest": true,
"jquery": true,
"node": true
},
"extends": [
"airbnb-base",
"plugin:jsx-a11y/recommended",
"plugin:mdx/recommended",
"eslint:recommended",
"plugin:astro/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"globals": {
"$": true,
"jQuery": true
},
"overrides": [
{
"files": ["*.astro"],
"parser": "astro-eslint-parser",
"parserOptions": {
"extraFileExtensions": [".astro"],
"jsx": true,
"parser": "@typescript-eslint/parser",
"rules": {
// Override rules for Astro files
}
},
"rules": {
// Custom rules
"astro/no-conflict-set-directives": "error",
"astro/no-unused-define-vars-in-style": "error",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/heading-has-content": "off",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
"react/no-unknown-property": [
"error",
{
"ignore": [
"class",
"set:html",
"define:vars",
"is:raw",
"transition:animate",
"transition:fade",
"transition:slide",
"transition:persist",
"clip-rule"
]
}
]
}
},
{
"files": ["*.mdx"],
"parser": "eslint-mdx",
"rules": {
"@typescript-eslint/no-unused-vars": "off",
"no-unused-expressions": "off",
"react/jsx-no-undef": "off"
}
},
{
"excludedFiles": [
"assets/js/bundle.js",
"assets/dist/**",
"stencil.conf.js",
"webpack.*.js",
"**/*.min.js",
"**/*.min.js.map"
],
"files": ["*.js", "*.jsx", "*.ts", "*.tsx"]
},
{
"env": {
"node": true
},
"files": ["webpack.*.js", "*.config.js", "*.config.mjs", "*.config.ts", "*.config.js", "astro.config.mjs"],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"import/no-extraneous-dependencies": "off"
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "prettier", "react-hooks", "jsx-a11y", "react", "@typescript-eslint"],
"root": true,
"rules": {
"@typescript-eslint/consistent-type-imports": [
"warn",
{
"prefer": "type-imports"
}
],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-shadow": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-use-before-define": "error",
"@typescript-eslint/no-var-requires": "off",
"block-scoped-var": 0,
"class-methods-use-this": 0,
"consistent-return": 0,
"default-case": 0,
"default-param-last": 0,
"eqeqeq": ["warn", "smart"],
"func-names": 0,
"global-require": "off",
"import/extensions": [
"error",
"ignorePackages",
{
"astro": "always",
"js": "never",
"json": "always",
"jsx": "never",
"mdx": "never",
"ts": "never",
"tsx": "never"
}
],
"import/first": 0,
"import/no-cycle": 0,
"import/no-named-as-default": 0,
"import/no-named-as-default-member": 0,
"import/no-unresolved": [
"error",
{
"ignore": [
"@astrojs/image/components",
"^astro:",
"^@/",
"astro/config",
"@astrojs/react",
"@astrojs/tailwind",
"@astrojs/cloudflare",
"@astrojs/partytown"
]
}
],
"import/order": "off",
"import/prefer-default-export": "off",
"jsx-a11y/anchor-is-valid": [
"error",
{
"aspects": ["invalidHref", "preferButton"],
"components": ["Link"],
"specialLink": ["to", "hrefLeft", "hrefRight"]
}
],
"jsx-a11y/no-redundant-roles": "off",
"linebreak-style": ["error", "unix"],
"max-classes-per-file": 0,
"max-len": 0,
"max-lines": "off",
"new-cap": 0,
"newline-per-chained-call": 0,
"no-alert": 0,
"no-cond-assign": 0,
"no-const-assign": 0,
"no-constructor-return": 0,
"no-empty-function": 0,
"no-inner-declarations": 0,
"no-loop-func": 0,
"no-mixed-operators": 0,
"no-mixed-spaces-and-tabs": 2,
"no-multi-assign": 0,
"no-multi-str": 0,
"no-new": 0,
"no-param-reassign": 0,
"no-plusplus": 0,
"no-prototype-builtins": 0,
"no-redeclare": 0,
"no-restricted-globals": 0,
"no-restricted-syntax": 0,
"no-shadow": "off",
"no-template-curly-in-string": 0,
"no-undef": 0,
"no-undef-init": 0,
"no-underscore-dangle": 0,
"no-unused-expressions": 0,
"no-unused-vars": "off",
"no-use-before-define": "off",
"no-useless-concat": 0,
"no-useless-constructor": 0,
"no-useless-escape": 0,
"no-useless-return": 0,
"no-var": "error",
"object-shorthand": 0,
"one-var": 0,
"one-var-declaration-per-line": 0,
"operator-assignment": 0,
"prefer-arrow-callback": 0,
"prefer-const": "warn",
"prefer-destructuring": 0,
"prefer-rest-params": 0,
"prefer-spread": 0,
"prettier/prettier": [
"error",
{
"printWidth": 120,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "all"
}
],
"quote-props": 0,
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"react-hooks/exhaustive-deps": "warn",
"react-hooks/rules-of-hooks": "error",
"react/no-unescaped-entities": "off",
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"semi": ["error", "always"],
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": "off",
"sonarjs/cognitive-complexity": 0,
"sonarjs/prefer-optional-chain": 0,
"sort-imports": "off",
"vars-on-top": 0
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx", ".astro"]
}
},
"mdx": {
"code-blocks": true
},
"react": {
"version": "detect"
}
}
}

The Prettier configuration is in .prettierrc.json:

{
"arrowParens": "avoid",
"bracketSameLine": true,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"experimentalOperatorPosition": "end",
"experimentalTernaries": true,
"htmlWhitespaceSensitivity": "css",
"jsxBracketSameLine": false,
"objectWrap": "preserve",
"overrides": [
{
"files": ["*.astro"],
"options": {
"parser": "astro",
"printWidth": 120,
"tabWidth": 4
}
},
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs"],
"options": {
"parser": "babel-ts",
"printWidth": 120,
"singleQuote": true,
"tabWidth": 4
}
},
{
"files": ["*.handlebars", "*.hbs"],
"options": {
"parser": "glimmer",
"printWidth": 120,
"singleQuote": false
}
},
{
"files": "*.html",
"options": {
"embeddedLanguageFormatting": "off",
"htmlWhitespaceSensitivity": "strict",
"parser": "html",
"printWidth": 2000,
"singleAttributePerLine": true,
"singleQuote": false,
"tabWidth": 4
}
},
{
"files": "*.md",
"options": {
"parser": "markdown",
"printWidth": 120
}
},
{
"files": ["*.scss", "*.less", "*.sass", "*.css"],
"options": {
"parser": "scss",
"printWidth": 200,
"singleQuote": true,
"tabWidth": 4
}
},
{
"files": ["*.json", "*.jsonc"],
"options": {
"parser": "json",
"printWidth": 120,
"trailingComma": "none"
}
},
{
"files": ["*.yaml", "*.yml"],
"options": {
"parser": "yaml",
"printWidth": 120,
"tabWidth": 2
}
}
],
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss-extra-plus", "prettier-plugin-tailwindcss"],
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"semi": true,
"singleAttributePerLine": false,
"singleQuote": true,
"tabWidth": 2,
"tailwindConfig": "tailwind.config.mjs",
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": true
}

The Stylelint configuration is in .stylelintrc.json:

{
"extends": ["stylelint-config-standard", "stylelint-config-tailwindcss"],
"rules": {
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen", "layer"]
}
],
"declaration-block-trailing-semicolon": null,
"no-descending-specificity": null
}
}

Package.json scripts:

{
"scripts": {
"build": "astro build",
"check": "astro check && tsc --noEmit",
"dev": "astro dev",
"format": "prettier --write .",
"lint": "npm run lint:js && npm run lint:style",
"lint:js": "eslint . --ext .js,.jsx,.ts,.tsx,.astro",
"lint:style": "stylelint \"src/**/*.{css,scss,astro}\"",
"preview": "astro preview",
"start": "astro dev"
}
}

Create React components in the src/components/react/ directory:

src/components/react/Counter.jsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div className='py-4'>
<p className='text-lg font-medium'>Count: {count}</p>
<button
onClick={() => setCount(count + 1)}
className='mt-2 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700'>
Increment
</button>
</div>
);
}

Use the React component in an Astro page:

src/pages/index.astro
---
import Layout from '../layouts/Layout.astro';
import Counter from '../components/react/Counter';
---
<Layout title='Home'>
<main class='mx-auto max-w-4xl p-4'>
<h1 class='mb-4 text-4xl font-bold'>Welcome to Astro with React and Tailwind CSS</h1>
<p class='mb-4'>This is a template for building websites with Astro, React, and Tailwind CSS.</p>
<Counter client:visible />
</main>
</Layout>

Create Markdown content in the src/pages/posts/ directory:

---
layout: ../../layouts/BlogPostLayout.astro
title: Getting Started with Astro
publishDate: 2023-05-15
description: Learn how to get started with Astro and this template
---
# Getting Started with Astro
This is a sample blog post written in Markdown.
## Features
- **Fast**: Astro builds websites that ship less JavaScript
- **Flexible**: Supports React, Vue, Svelte, and more
- **UI-agnostic**: Use any frontend framework you want

Create a blog post layout:

src/layouts/BlogPostLayout.astro
---
import Layout from './Layout.astro';
const { frontmatter } = Astro.props;
---
<Layout title={frontmatter.title}>
<article class='mx-auto max-w-3xl p-4'>
<h1 class='mb-2 text-3xl font-bold'>{frontmatter.title}</h1>
<p class='mb-6 text-gray-500'>Published on {frontmatter.publishDate}</p>
<div class='prose prose-lg max-w-none'>
<slot />
</div>
</article>
</Layout>
Terminal window
npm run build
# or
yarn build

The build output will be in the dist/ directory, which you can deploy to your hosting platform of choice.

This template is optimized for deployment on Cloudflare Pages:

  1. Push your code to GitHub or GitLab repository

  2. Connect your repository to Cloudflare Pages:

    • Log in to the Cloudflare dashboard
    • Go to Pages > Create a project
    • Select your repository and set up the project
  3. Configure build settings:

    • Build command: npm run build
    • Build output directory: dist
    • Node.js version: 16 (or newer)
  4. Environment variables (optional):

    • You can add environment variables in the Cloudflare Pages dashboard
  5. Deploy:

    • Cloudflare Pages will automatically build and deploy your site
    • Each commit to your main branch will trigger a new production deployment
    • Pull requests will create preview deployments
  6. Custom domain (optional):

    • Add a custom domain in the Cloudflare Pages settings
  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

This project is licensed under the MIT License - see the LICENSE file for details.