master 2b6144fa1553 cached
74 files
256.9 KB
67.0k tokens
19 symbols
1 requests
Download .txt
Showing preview only (279K chars total). Download the full file or copy to clipboard to get everything.
Repository: tailwindlabs/blog.tailwindcss.com
Branch: master
Commit: 2b6144fa1553
Files: 74
Total size: 256.9 KB

Directory structure:
gitextract_xwaefze3/

├── .gitignore
├── README.md
├── jsconfig.json
├── next.config.js
├── package.json
├── postcss.config.js
├── prettier.config.js
├── public/
│   ├── browserconfig.xml
│   └── site.webmanifest
├── remark/
│   ├── vendor/
│   │   └── prism-diff-highlight.js
│   ├── withProse.js
│   └── withSyntaxHighlighting.js
├── scripts/
│   └── build-rss.js
├── src/
│   ├── authors.js
│   ├── components/
│   │   ├── Header.js
│   │   ├── PageTitle.js
│   │   ├── Post.js
│   │   └── SectionContainer.js
│   ├── css/
│   │   ├── prism.css
│   │   └── tailwind.css
│   ├── getAllPostPreviews.js
│   ├── getStaticProps.js
│   └── pages/
│       ├── _app.js
│       ├── _document.js
│       ├── building-react-and-vue-support-for-tailwind-ui/
│       │   └── index.mdx
│       ├── building-the-tailwind-blog/
│       │   └── index.mdx
│       ├── designing-tailwind-ui-ecommerce/
│       │   └── index.mdx
│       ├── from-900-to-1-how-we-hired-robin-malfait/
│       │   └── index.mdx
│       ├── headless-ui-unstyled-accessible-ui-components/
│       │   └── index.mdx
│       ├── headless-ui-v1/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── headless-ui-v1-4/
│       │   ├── .prettierrc
│       │   ├── index.mdx
│       │   └── snippets/
│       │       ├── react-1.mdx
│       │       ├── react-2.mdx
│       │       ├── react-3.mdx
│       │       ├── react-4.mdx
│       │       ├── vue-1.mdx
│       │       ├── vue-2.mdx
│       │       ├── vue-3.mdx
│       │       └── vue-4.mdx
│       ├── heroicons-v1/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── index.js
│       ├── introducing-heroicons/
│       │   └── index.mdx
│       ├── introducing-linting-for-tailwindcss-intellisense/
│       │   └── index.mdx
│       ├── introducing-tailwind-play/
│       │   └── index.mdx
│       ├── just-in-time-the-next-generation-of-tailwind-css/
│       │   └── index.mdx
│       ├── multi-line-truncation-with-tailwindcss-line-clamp/
│       │   └── index.mdx
│       ├── simon-vrachliotis-joins-tailwind-labs/
│       │   └── index.mdx
│       ├── tailwind-ui-ecommerce/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwind-ui-now-with-react-and-vue-support/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-1-5/
│       │   └── index.mdx
│       ├── tailwindcss-1-6/
│       │   └── index.mdx
│       ├── tailwindcss-1-7/
│       │   └── index.mdx
│       ├── tailwindcss-1-8/
│       │   └── index.mdx
│       ├── tailwindcss-1-9/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-2-1/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-2-2/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-from-zero-to-production/
│       │   └── index.mdx
│       ├── tailwindcss-typography/
│       │   └── index.mdx
│       ├── tailwindcss-v2/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── utility-friendly-transitions-with-tailwindui-react/
│       │   └── index.mdx
│       ├── welcoming-brad-cornes-to-the-tailwind-team/
│       │   └── index.mdx
│       ├── welcoming-david-luhr-to-tailwind-labs/
│       │   └── index.mdx
│       ├── welcoming-james-mcdonald-to-tailwind-labs/
│       │   └── index.mdx
│       └── whats-new-in-tailwindcss-on-youtube/
│           └── index.mdx
└── tailwind.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local


================================================
FILE: README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


================================================
FILE: jsconfig.json
================================================
{
  "compilerOptions": {
    "baseUrl": "node_modules",
    "paths": {
      "@/*": ["../src/*"]
    }
  }
}


================================================
FILE: next.config.js
================================================
const { createLoader } = require('simple-functional-loader')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
const withSyntaxHighlighting = require('./remark/withSyntaxHighlighting')
const withProse = require('./remark/withProse')

module.exports = withBundleAnalyzer({
  pageExtensions: ['js', 'jsx', 'mdx'],
  experimental: {
    modern: true,
  },
  images: {
    disableStaticImages: true,
  },
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.(svg|png|jpe?g|gif|mp4)$/i,
      use: [
        {
          loader: 'file-loader',
          options: {
            publicPath: '/_next',
            name: 'static/media/[name].[hash].[ext]',
          },
        },
      ],
    })

    const mdx = [
      options.defaultLoaders.babel,
      {
        loader: '@mdx-js/loader',
        options: {
          remarkPlugins: [withProse, withSyntaxHighlighting],
        },
      },
    ]

    config.module.rules.push({
      test: /\.mdx$/,
      oneOf: [
        {
          test: /snippets/,
          use: mdx,
        },
        {
          resourceQuery: /preview/,
          use: [
            ...mdx,
            createLoader(function (src) {
              if (src.includes('<!--more-->')) {
                const [preview] = src.split('<!--more-->')
                return this.callback(null, preview)
              }

              const [preview] = src.split('<!--/excerpt-->')
              return this.callback(null, preview.replace('<!--excerpt-->', ''))
            }),
          ],
        },
        {
          resourceQuery: /rss/,
          use: mdx,
        },
        {
          use: [
            ...mdx,
            createLoader(function (src) {
              const content = [
                'import Post from "@/components/Post"',
                'export { getStaticProps } from "@/getStaticProps"',
                src,
                'export default Post',
              ].join('\n')

              if (content.includes('<!--more-->')) {
                return this.callback(null, content.split('<!--more-->').join('\n'))
              }

              return this.callback(null, content.replace(/<!--excerpt-->.*<!--\/excerpt-->/s, ''))
            }),
          ],
        },
      ],
    })

    if (!options.dev && options.isServer) {
      const originalEntry = config.entry

      config.entry = async () => {
        const entries = { ...(await originalEntry()) }
        entries['scripts/build-rss'] = './scripts/build-rss.js'
        return entries
      }
    }

    return config
  },
})


================================================
FILE: package.json
================================================
{
  "name": "tailwind-blog",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build && next export && npm run build:rss",
    "start": "next start",
    "build:rss": "node ./.next/server/scripts/build-rss.js"
  },
  "dependencies": {
    "@headlessui/react": "^1.4.0",
    "@mdx-js/loader": "^1.6.22",
    "@tailwindcss/aspect-ratio": "^0.2.1",
    "@tailwindcss/typography": "^0.4.0",
    "autoprefixer": "^10.2.5",
    "next": "^11.1.2",
    "postcss": "^8.2.8",
    "prismjs": "^1.25.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "tailwindcss": "^2.2.0-canary.16",
    "tinytime": "^0.2.6"
  },
  "devDependencies": {
    "@next/bundle-analyzer": "^10.0.8",
    "feed": "^4.2.1",
    "file-loader": "^6.0.0",
    "simple-functional-loader": "^1.2.1"
  }
}


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}


================================================
FILE: prettier.config.js
================================================
module.exports = {
  semi: false,
  singleQuote: true,
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
  trailingComma: 'es5',
  bracketSpacing: true,
}


================================================
FILE: public/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
    <msapplication>
        <tile>
            <square150x150logo src="/mstile-150x150.png"/>
            <TileColor>#00aba9</TileColor>
        </tile>
    </msapplication>
</browserconfig>


================================================
FILE: public/site.webmanifest
================================================
{
    "name": "",
    "short_name": "",
    "icons": [
        {
            "src": "/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ],
    "theme_color": "#ffffff",
    "background_color": "#ffffff",
    "display": "standalone"
}


================================================
FILE: remark/vendor/prism-diff-highlight.js
================================================
// https://github.com/PrismJS/prism/blob/master/plugins/diff-highlight/prism-diff-highlight.js
module.exports = (Prism) => {
  var LANGUAGE_REGEX = /diff-([\w-]+)/i
  var HTML_TAG =
    /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/gi
  //this will match a line plus the line break while ignoring the line breaks HTML tags may contain.
  var HTML_LINE = RegExp(
    /(?:__|[^\r\n<])*(?:\r\n?|\n|(?:__|[^\r\n<])(?![^\r\n]))/.source.replace(/__/g, function () {
      return HTML_TAG.source
    }),
    'gi'
  )

  var PREFIXES = Prism.languages.diff.PREFIXES

  Prism.hooks.add('before-sanity-check', function (env) {
    var lang = env.language
    if (LANGUAGE_REGEX.test(lang) && !env.grammar) {
      env.grammar = Prism.languages[lang] = Prism.languages['diff']
    }
  })
  Prism.hooks.add('before-tokenize', function (env) {
    var lang = env.language
    if (LANGUAGE_REGEX.test(lang) && !Prism.languages[lang]) {
      Prism.languages[lang] = Prism.languages['diff']
    }
  })

  Prism.hooks.add('wrap', function (env) {
    var diffLanguage, diffGrammar

    if (env.language !== 'diff') {
      var langMatch = LANGUAGE_REGEX.exec(env.language)
      if (!langMatch) {
        return // not a language specific diff
      }

      diffLanguage = langMatch[1]
      diffGrammar = Prism.languages[diffLanguage]
    }

    // one of the diff tokens without any nested tokens
    if (env.type in PREFIXES) {
      /** @type {string} */
      var content = env.content.replace(HTML_TAG, '') // remove all HTML tags

      /** @type {string} */
      var decoded = content.replace(/&lt;/g, '<').replace(/&amp;/g, '&')

      // remove any one-character prefix
      var code = decoded.replace(/(^|[\r\n])./g, '$1')

      // highlight, if possible
      var highlighted
      if (diffGrammar) {
        highlighted = Prism.highlight(code, diffGrammar, diffLanguage)
      } else {
        highlighted = Prism.util.encode(code)
      }

      // get the HTML source of the prefix token
      var prefixToken = new Prism.Token('prefix', PREFIXES[env.type], [/\w+/.exec(env.type)[0]])
      var prefix = Prism.Token.stringify(prefixToken, env.language)

      // add prefix
      var lines = [],
        m
      HTML_LINE.lastIndex = 0
      while ((m = HTML_LINE.exec(highlighted))) {
        lines.push(prefix + m[0])
      }
      if (/(?:^|[\r\n]).$/.test(decoded)) {
        // because both "+a\n+" and "+a\n" will map to "a\n" after the line prefixes are removed
        lines.push(prefix)
      }
      env.content = lines.join('')

      if (diffGrammar) {
        env.classes.push('language-' + diffLanguage)
      }
    }
  })
}


================================================
FILE: remark/withProse.js
================================================
const proseComponents = ['Heading']

const isJsNode = (node) => {
  return (
    ['jsx', 'import', 'export'].includes(node.type) &&
    !/^<[a-z]+(>|\s)/.test(node.value) &&
    !new RegExp(`^<(${proseComponents.join('|')})(>|\\s)`).test(node.value)
  )
}

module.exports = function () {
  return (tree) => {
    let insideProse = false
    tree.children = tree.children.flatMap((node, i) => {
      if (insideProse && isJsNode(node)) {
        insideProse = false
        return [{ type: 'jsx', value: '</div>' }, node]
      }
      if (!insideProse && !isJsNode(node)) {
        insideProse = true
        return [
          { type: 'jsx', value: '<div className="prose max-w-none">' },
          node,
          ...(i === tree.children.length - 1 ? [{ type: 'jsx', value: '</div>' }] : []),
        ]
      }
      if (i === tree.children.length - 1 && insideProse) {
        return [node, { type: 'jsx', value: '</div>' }]
      }
      return [node]
    })
  }
}


================================================
FILE: remark/withSyntaxHighlighting.js
================================================
const visit = require('unist-util-visit')
const Prism = require('prismjs')
const loadLanguages = require('prismjs/components/')
loadLanguages()
require('./vendor/prism-diff-highlight')(Prism)

function highlightCode(code, prismLanguage) {
  const isDiff = prismLanguage.startsWith('diff-')
  const language = isDiff ? prismLanguage.substr(5) : prismLanguage
  const grammar = Prism.languages[isDiff ? 'diff' : language]
  if (!grammar) {
    console.warn(`Unrecognised language: ${prismLanguage}`)
    return Prism.util.encode(code)
  }
  let highlighted = Prism.highlight(code, grammar, prismLanguage)

  return language === 'html'
    ? highlighted.replace(
        /\*\*(.*?)\*\*/g,
        (_, text) => `<span class="code-highlight bg-code-highlight">${text}</span>`
      )
    : highlighted
}

module.exports = function withSyntaxHighlighting() {
  return (tree) => {
    visit(tree, 'code', (node) => {
      if (node.lang !== null) {
        node.type = 'html'
        node.value = [
          `<pre class="language-${node.lang}">`,
          `<code class="language-${node.lang}">`,
          highlightCode(node.value, node.lang),
          '</code>',
          '</pre>',
        ]
          .filter(Boolean)
          .join('')
      }
    })
  }
}


================================================
FILE: scripts/build-rss.js
================================================
import fs from 'fs'
import ReactDOMServer from 'react-dom/server'
import { MDXProvider } from '@mdx-js/react'
import { Feed } from 'feed'

import { getAllPosts } from '../src/getAllPostPreviews'

const siteUrl = 'https://blog.tailwindcss.com'

const feed = new Feed({
  title: 'Tailwind CSS Blog',
  description: 'All the latest Tailwind CSS news, straight from the team.',
  id: siteUrl,
  link: siteUrl,
  language: 'en',
  image: `${siteUrl}/favicon-32x32.png`,
  favicon: `${siteUrl}/favicon.ico`,
  copyright: `All rights reserved ${new Date().getFullYear()}, Tailwind Labs`,
  feedLinks: {
    rss: `${siteUrl}/feed.xml`,
    json: `${siteUrl}/feed.json`,
    atom: `${siteUrl}/atom.xml`,
  },
  author: {
    name: 'Adam Wathan',
    link: 'https://twitter.com/@adamwathan',
  },
})

getAllPosts().forEach(({ link, module: { meta, default: Content } }) => {
  const mdx = (
    <MDXProvider>
      <Content />
    </MDXProvider>
  )
  const html = ReactDOMServer.renderToStaticMarkup(mdx)
  const postText = `<p><em>(The post <a href="${siteUrl + link}">${
    meta.title
  }</a> appeared first on <a href="${siteUrl}">Tailwind CSS Blog</a>.)</em></p>`
  feed.addItem({
    title: meta.title,
    id: meta.title,
    link,
    description: meta.description,
    content: html + postText,
    author: meta.authors.map(({ name, twitter }) => ({
      name,
      link: `https://twitter.com/${twitter}`,
    })),
    date: new Date(meta.date),
    image: siteUrl + meta.image,
    ...(meta.discussion
      ? {
          comments: meta.discussion,
          extensions: [
            {
              name: '_comments',
              objects: {
                about: 'Link to discussion forum',
                comments: meta.discussion,
              },
            },
          ],
        }
      : {}),
  })
})

fs.writeFileSync('./out/feed.xml', feed.rss2())
fs.writeFileSync('./out/atom.xml', feed.atom1())
fs.writeFileSync('./out/feed.json', feed.json1())


================================================
FILE: src/authors.js
================================================
import adamwathanAvatar from './img/adamwathan.jpg'
import bradlcAvatar from './img/bradlc.jpg'
import steveschogerAvatar from './img/steveschoger.jpg'
import robinmalfaitAvatar from './img/robinmalfait.jpg'
import simonswissAvatar from './img/simonswiss.jpg'

export const adamwathan = {
  name: 'Adam Wathan',
  twitter: 'adamwathan',
  avatar: adamwathanAvatar,
}

export const bradlc = {
  name: 'Brad Cornes',
  twitter: 'bradlc',
  avatar: bradlcAvatar,
}

export const steveschoger = {
  name: 'Steve Schoger',
  twitter: 'steveschoger',
  avatar: steveschogerAvatar,
}

export const robinmalfait = {
  name: 'Robin Malfait',
  twitter: 'malfaitrobin',
  avatar: robinmalfaitAvatar,
}

export const simonswiss = {
  name: 'Simon Vrachliotis',
  twitter: 'simonswiss',
  avatar: simonswissAvatar,
}


================================================
FILE: src/components/Header.js
================================================
import Link from 'next/link'

export function TailwindMark({ className }) {
  return (
    <svg className={className} fill="none" viewBox="0 0 55 33">
      <path fill="#fff" d="M0 0h55v33H0z" />
      <path
        fill="#06B6D4"
        fillRule="evenodd"
        d="M27.5 0c-7.333 0-11.917 3.667-13.75 11 2.75-3.667 5.958-5.042 9.625-4.125 2.092.523 3.587 2.04 5.242 3.72 2.697 2.737 5.817 5.905 12.633 5.905 7.333 0 11.917-3.667 13.75-11-2.75 3.667-5.958 5.042-9.625 4.125-2.092-.523-3.587-2.04-5.242-3.72C37.436 3.166 34.316 0 27.5 0zM13.75 16.5c-7.333 0-11.917 3.667-13.75 11 2.75-3.667 5.958-5.042 9.625-4.125 2.092.523 3.587 2.04 5.242 3.72C17.564 29.834 20.684 33 27.5 33c7.333 0 11.917-3.667 13.75-11-2.75 3.667-5.958 5.042-9.625 4.125-2.092-.523-3.587-2.04-5.242-3.72-2.697-2.738-5.817-5.905-12.633-5.905z"
        clipRule="evenodd"
      />
    </svg>
  )
}

export function TailwindLogo({ className }) {
  return (
    <svg className={className} fill="none" viewBox="0 0 285 33">
      <path
        fill="#06B6D4"
        fillRule="evenodd"
        d="M27.5 0c-7.333 0-11.917 3.667-13.75 11 2.75-3.667 5.958-5.042 9.625-4.125 2.092.523 3.587 2.04 5.242 3.72 2.697 2.737 5.817 5.905 12.633 5.905 7.333 0 11.917-3.667 13.75-11-2.75 3.667-5.958 5.042-9.625 4.125-2.092-.523-3.587-2.04-5.242-3.72C37.436 3.166 34.316 0 27.5 0zM13.75 16.5c-7.333 0-11.917 3.667-13.75 11 2.75-3.667 5.958-5.042 9.625-4.125 2.092.523 3.587 2.04 5.242 3.72C17.564 29.834 20.684 33 27.5 33c7.333 0 11.917-3.667 13.75-11-2.75 3.667-5.958 5.042-9.625 4.125-2.092-.523-3.587-2.04-5.242-3.72-2.697-2.738-5.817-5.905-12.633-5.905z"
        clipRule="evenodd"
      />
      <path
        fill="#374151"
        d="M80.496 14.692V10.74h-4.712V4.42L71.68 5.636v5.104h-3.496v3.952h3.496v9.12c0 4.94 2.508 6.688 8.816 5.928v-3.686c-3.116.152-4.712.19-4.712-2.242v-9.12h4.712zM98.487 10.74v2.698c-1.444-1.976-3.686-3.192-6.65-3.192-5.168 0-9.462 4.332-9.462 9.994 0 5.624 4.294 9.994 9.462 9.994 2.964 0 5.206-1.216 6.65-3.23v2.736h4.104v-19h-4.104zm-6.004 15.58c-3.42 0-6.004-2.546-6.004-6.08 0-3.534 2.584-6.08 6.004-6.08 3.42 0 6.004 2.546 6.004 6.08 0 3.534-2.584 6.08-6.004 6.08zM109.426 7.89c1.444 0 2.622-1.216 2.622-2.622a2.627 2.627 0 00-2.622-2.622 2.627 2.627 0 00-2.622 2.622c0 1.406 1.178 2.622 2.622 2.622zm-2.052 21.85h4.104v-19h-4.104v19zM116.244 29.74h4.104V2h-4.104v27.74zM147.001 10.74l-3.724 13.11-3.952-13.11h-3.914l-3.99 13.11-3.686-13.11h-4.332l5.966 19h4.028l3.99-12.806 3.952 12.806h4.028l5.966-19h-4.332zM156.407 7.89c1.444 0 2.622-1.216 2.622-2.622a2.627 2.627 0 00-2.622-2.622 2.627 2.627 0 00-2.622 2.622c0 1.406 1.178 2.622 2.622 2.622zm-2.052 21.85h4.104v-19h-4.104v19zM173.218 10.246c-2.584 0-4.636.95-5.89 2.926V10.74h-4.104v19h4.104V19.556c0-3.876 2.128-5.472 4.826-5.472 2.584 0 4.256 1.52 4.256 4.408V29.74h4.104V18.074c0-4.94-3.04-7.828-7.296-7.828zM199.981 3.14v10.298c-1.444-1.976-3.686-3.192-6.65-3.192-5.168 0-9.462 4.332-9.462 9.994 0 5.624 4.294 9.994 9.462 9.994 2.964 0 5.206-1.216 6.65-3.23v2.736h4.104V3.14h-4.104zm-6.004 23.18c-3.42 0-6.004-2.546-6.004-6.08 0-3.534 2.584-6.08 6.004-6.08 3.42 0 6.004 2.546 6.004 6.08 0 3.534-2.584 6.08-6.004 6.08z"
      />
      <path
        fill="#06B6D4"
        d="M221.249 19.884c1.113-.895 1.792-2.172 1.792-3.8 0-3.177-2.579-5.349-5.783-5.349h-7.493V29.74h8.063c3.285 0 5.919-2.253 5.919-5.511 0-1.982-.977-3.476-2.498-4.345zm-3.991-5.647c1.195 0 2.036.896 2.036 2.09 0 1.195-.868 2.091-2.036 2.091h-3.747v-4.18h3.747zm.57 12h-4.317v-4.48h4.317c1.276 0 2.199.951 2.199 2.227 0 1.303-.923 2.254-2.199 2.254zM232.122 26.156V10.735h-3.747V29.74h10.996v-3.584h-7.249zM251.492 30.12c5.484 0 9.882-4.344 9.882-9.883 0-5.565-4.398-9.882-9.882-9.882-5.485 0-9.856 4.317-9.856 9.883 0 5.538 4.371 9.882 9.856 9.882zm0-3.665c-3.448 0-6.136-2.58-6.136-6.217 0-3.666 2.688-6.245 6.136-6.245s6.135 2.58 6.135 6.245c0 3.638-2.687 6.217-6.135 6.217zM284.832 19.532h-9.53v3.312h5.756c-.652 2.172-2.552 3.61-5.539 3.61-3.964 0-6.489-2.66-6.489-6.19 0-3.61 2.607-6.271 6.218-6.271 2.307 0 4.235 1.113 5.104 2.633l3.204-1.846c-1.602-2.606-4.67-4.425-8.281-4.425-5.675 0-9.964 4.398-9.964 9.91 0 5.43 4.235 9.855 10.181 9.855 5.457 0 9.34-3.638 9.34-9.122v-1.466z"
      />
    </svg>
  )
}

export default function Header() {
  return (
    <header className="flex justify-between items-center py-10">
      <div>
        <Link href="/">
          <a aria-label="Tailwind CSS Blog">
            <TailwindMark className="h-6 sm:hidden" />
            <TailwindLogo className="hidden sm:block h-6" />
          </a>
        </Link>
      </div>
      <div className="text-base leading-5">
        <a
          href="https://tailwindcss.com/docs"
          className="font-medium text-gray-500 hover:text-gray-700"
        >
          Documentation &rarr;
        </a>
      </div>
    </header>
  )
}


================================================
FILE: src/components/PageTitle.js
================================================
export default function PageTitle({ children }) {
  return (
    <h1 className="text-3xl font-extrabold text-gray-900 tracking-tight sm:text-4xl md:text-5xl md:leading-[3.5rem]">
      {children.replace(/ ([^ ]+)$/, '\u00A0$1')}
    </h1>
  )
}


================================================
FILE: src/components/Post.js
================================================
import Head from 'next/head'
import PageTitle from '@/components/PageTitle'
import tinytime from 'tinytime'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { MDXProvider } from '@mdx-js/react'
import Header, { TailwindMark } from '@/components/Header'
import SectionContainer from '@/components/SectionContainer'
import smallCard from '@/img/twitter-card-small.jpg'

const postDateTemplate = tinytime('{dddd}, {MMMM} {DD}, {YYYY}')

export default function Post({ meta, children, posts }) {
  const router = useRouter()

  if (meta.private) {
    return (
      <>
        <SectionContainer>
          <main>
            <article className="py-16">
              <Head>
                <title>{meta.title} – Tailwind CSS</title>
                <meta name="twitter:site" content="@tailwindcss" />
                <meta name="twitter:creator" content="@tailwindcss" />
                <meta name="twitter:title" content={`${meta.title} – Tailwind CSS`} />
                <meta name="twitter:description" content={meta.description} />
                {meta.image ? (
                  <>
                    <meta name="twitter:card" content="summary_large_image" />
                    <meta
                      name="twitter:image"
                      content={`https://blog.tailwindcss.com${meta.image}`}
                    />
                  </>
                ) : (
                  <>
                    <meta name="twitter:card" content="summary" />
                    <meta
                      name="twitter:image"
                      content={`https://blog.tailwindcss.com${smallCard}`}
                    />
                  </>
                )}
                <meta
                  property="og:url"
                  content={`https://blog.tailwindcss.com${router.pathname}`}
                />
                <meta property="og:type" content="article" />
                <meta property="og:title" content={`${meta.title} – Tailwind CSS`} />
                <meta property="og:description" content={meta.description} />
                <meta property="og:image" content={`https://blog.tailwindcss.com${meta.image}`} />
                <meta name="description" content={meta.description}></meta>
              </Head>
              <header className="">
                <div className="text-center">
                  <div className="flex justify-center">
                    <Link href="/">
                      <a className="inline-flex">
                        <span className="sr-only">All posts</span>
                        <TailwindMark className="h-12 w-12" />
                      </a>
                    </Link>
                  </div>
                  <dl className="mt-4 space-y-10">
                    <div>
                      <dt className="sr-only">Published on</dt>
                      <dd className="text-base font-medium text-gray-500">
                        <time dateTime={meta.date}>
                          {postDateTemplate.render(new Date(meta.date))}
                        </time>
                      </dd>
                    </div>
                  </dl>
                  <div className="mt-1">
                    <h1 className="text-2xl leading-8 font-extrabold text-gray-900 tracking-tight sm:text-3xl sm:leading-9">
                      {meta.title.replace(/ ([^ ]+)$/, '\u00A0$1')}
                    </h1>
                  </div>
                  <dl className="mt-4">
                    <dt className="sr-only">Authors</dt>
                    <dd>
                      <ul className="flex items-center justify-center">
                        {meta.authors.map((author) => (
                          <li key={author.twitter} className="flex items-center space-x-2">
                            <img src={author.avatar} alt="" className="w-8 h-8 rounded-full" />
                            <dl className="text-sm font-medium whitespace-no-wrap">
                              <dt className="sr-only">Name</dt>
                              <dd className="text-gray-900">{author.name}</dd>
                            </dl>
                          </li>
                        ))}
                      </ul>
                    </dd>
                  </dl>
                </div>
              </header>
              <div className="mt-12">
                <div className="prose mx-auto">
                  <MDXProvider>{children}</MDXProvider>
                </div>
              </div>
            </article>
          </main>
        </SectionContainer>
      </>
    )
  }

  const postIndex = posts.findIndex((post) => post.link === router.pathname)
  const previous = posts[postIndex + 1]
  const next = posts[postIndex - 1]

  return (
    <>
      <SectionContainer>
        <Header />
      </SectionContainer>
      <SectionContainer>
        <main>
          <article className="xl:divide-y xl:divide-gray-200">
            <Head>
              <title>{meta.title} – Tailwind CSS</title>
              <meta name="twitter:card" content="summary_large_image" />
              <meta name="twitter:site" content="@tailwindcss" />
              <meta name="twitter:creator" content="@tailwindcss" />
              <meta name="twitter:title" content={`${meta.title} – Tailwind CSS`} />
              <meta name="twitter:description" content={meta.description} />
              <meta name="twitter:image" content={`https://blog.tailwindcss.com${meta.image}`} />
              <meta property="og:url" content={`https://blog.tailwindcss.com${router.pathname}`} />
              <meta property="og:type" content="article" />
              <meta property="og:title" content={`${meta.title} – Tailwind CSS`} />
              <meta property="og:description" content={meta.description} />
              <meta property="og:image" content={`https://blog.tailwindcss.com${meta.image}`} />
              <meta name="description" content={meta.description}></meta>
            </Head>
            <header className="pt-6 xl:pb-10">
              <div className="space-y-1 text-center">
                <dl className="space-y-10">
                  <div>
                    <dt className="sr-only">Published on</dt>
                    <dd className="text-base leading-6 font-medium text-gray-500">
                      <time dateTime={meta.date}>
                        {postDateTemplate.render(new Date(meta.date))}
                      </time>
                    </dd>
                  </div>
                </dl>
                <div>
                  <PageTitle>{meta.title}</PageTitle>
                </div>
              </div>
            </header>
            <div
              className="divide-y xl:divide-y-0 divide-gray-200 xl:grid xl:grid-cols-4 xl:gap-x-6 pb-16 xl:pb-20"
              style={{ gridTemplateRows: 'auto 1fr' }}
            >
              <dl className="pt-6 pb-10 xl:pt-11 xl:border-b xl:border-gray-200">
                <dt className="sr-only">Authors</dt>
                <dd>
                  <ul className="flex justify-center xl:block space-x-8 sm:space-x-12 xl:space-x-0 xl:space-y-8">
                    {meta.authors.map((author) => (
                      <li key={author.twitter} className="flex items-center space-x-2">
                        <img src={author.avatar} alt="" className="w-10 h-10 rounded-full" />
                        <dl className="text-sm font-medium whitespace-no-wrap">
                          <dt className="sr-only">Name</dt>
                          <dd className="text-gray-900">{author.name}</dd>
                          <dt className="sr-only">Twitter</dt>
                          <dd>
                            <a
                              href={`https://twitter.com/${author.twitter}`}
                              className="text-teal-600 hover:text-teal-700"
                            >
                              @{author.twitter}
                            </a>
                          </dd>
                        </dl>
                      </li>
                    ))}
                  </ul>
                </dd>
              </dl>
              <div className="divide-y divide-gray-200 xl:pb-0 xl:col-span-3 xl:row-span-2">
                <div className="max-w-none pt-10 pb-8">
                  <MDXProvider>{children}</MDXProvider>
                </div>
                {meta.footer && (
                  <div className="pt-6 pb-16" dangerouslySetInnerHTML={{ __html: meta.footer }} />
                )}
                {!meta.footer && meta.discussion && (
                  <div className="pt-6 pb-16">
                    <p>
                      Want to talk about this post?{' '}
                      <a
                        href={meta.discussion}
                        className="font-medium text-teal-600 hover:text-teal-700"
                      >
                        Discuss this on GitHub &rarr;
                      </a>
                    </p>
                  </div>
                )}
              </div>
              <footer className="text-sm font-medium divide-y divide-gray-200 xl:col-start-1 xl:row-start-2">
                {(next || previous) && (
                  <div className="space-y-8 py-8">
                    {next && (
                      <div>
                        <h2 className="text-xs leading-5 tracking-wide uppercase text-gray-500">
                          Next Article
                        </h2>
                        <div className="text-teal-600 hover:text-teal-700">
                          <Link href={next.link}>
                            <a>{next.title}</a>
                          </Link>
                        </div>
                      </div>
                    )}
                    {previous && (
                      <div>
                        <h2 className="text-xs leading-5 tracking-wide uppercase text-gray-500">
                          Previous Article
                        </h2>
                        <div className="text-teal-600 hover:text-teal-700">
                          <Link href={previous.link}>
                            <a>{previous.title}</a>
                          </Link>
                        </div>
                      </div>
                    )}
                  </div>
                )}
                <div className="pt-8">
                  <Link href="/">
                    <a className="text-teal-600 hover:text-teal-700">&larr; Back to the blog</a>
                  </Link>
                </div>
              </footer>
            </div>
          </article>
        </main>
      </SectionContainer>
    </>
  )
}


================================================
FILE: src/components/SectionContainer.js
================================================
export default function SectionContainer({ children }) {
  return <div className="max-w-3xl mx-auto px-4 sm:px-6 xl:max-w-5xl xl:px-0">{children}</div>
}


================================================
FILE: src/css/prism.css
================================================
.language-javascript {
  color: white;
}

.token.tag,
.token.class-name,
.token.selector,
.token.selector .class,
.token.function {
  @apply text-fuchsia-400;
}

.token.attr-name,
.token.keyword,
.token.rule,
.token.operator,
.token.pseudo-class,
.token.important {
  @apply text-teal-400;
}

.token.attr-value,
.token.class,
.token.string,
.token.number,
.token.unit,
.token.color {
  @apply text-lime-300;
}

.token.punctuation,
.token.module,
.token.property {
  @apply text-sky-200;
}

.token.atapply .token:not(.rule):not(.important) {
  color: inherit;
}

.language-shell .token:not(.comment) {
  color: inherit;
}

.language-css .token.function {
  color: inherit;
}

.token.comment {
  @apply text-gray-400;
}

.token.deleted:not(.prefix) {
  @apply relative block -mx-4 px-4;
}

.token.deleted:not(.prefix)::after {
  content: '';
  @apply pointer-events-none absolute inset-0 block bg-rose-400 bg-opacity-25;
}

.token.deleted.prefix {
  @apply text-gray-400 select-none;
}

.token.inserted:not(.prefix) {
  @apply block bg-emerald-700 bg-opacity-50 -mx-4 px-4;
}

.token.inserted.prefix {
  @apply text-emerald-200 text-opacity-75 select-none;
}


================================================
FILE: src/css/tailwind.css
================================================
/* purgecss start ignore */
@tailwind base;
/* purgecss end ignore */

@tailwind components;
@tailwind utilities;

@import './prism.css';


================================================
FILE: src/getAllPostPreviews.js
================================================
function importAll(r) {
  return r.keys().map((fileName) => ({
    link: fileName.substr(1).replace(/\/index\.mdx$/, ''),
    module: r(fileName),
  }))
}

function dateSortDesc(a, b) {
  if (a > b) return -1
  if (a < b) return 1
  return 0
}

export default function getAllPostPreviews() {
  return importAll(require.context('./pages/?preview', true, /\.mdx$/))
    .filter((p) => !p.link.includes('/snippets/'))
    .filter((p) => p.module.meta.private !== true)
    .sort((a, b) => dateSortDesc(a.module.meta.date, b.module.meta.date))
}

export function getAllPosts() {
  return importAll(require.context('./pages/?rss', true, /\.mdx$/))
    .filter((p) => !p.link.includes('/snippets/'))
    .filter((p) => p.module.meta.private !== true)
    .sort((a, b) => dateSortDesc(a.module.meta.date, b.module.meta.date))
}


================================================
FILE: src/getStaticProps.js
================================================
import getAllPostPreviews from '@/getAllPostPreviews'

export async function getStaticProps() {
  return {
    props: {
      posts: getAllPostPreviews().map((post) => ({
        title: post.module.meta.title,
        link: post.link,
      })),
    },
  }
}


================================================
FILE: src/pages/_app.js
================================================
import '@/css/tailwind.css'
import Head from 'next/head'

export default function App({ Component, pageProps }) {
  return (
    <div className="antialiased">
      <Head>
        <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
        <link rel="manifest" href="/site.webmanifest" />
        <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
        <meta name="msapplication-TileColor" content="#00aba9" />
        <meta name="theme-color" content="#ffffff" />
        <link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="/feed.xml" />
        <link rel="alternate" type="application/atom+xml" title="Atom 1.0" href="/atom.xml" />
        <link rel="alternate" type="application/json" title="JSON Feed" href="/feed.json" />
      </Head>
      <Component {...pageProps} />
    </div>
  )
}


================================================
FILE: src/pages/_document.js
================================================
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import * as fs from 'fs'
import * as path from 'path'

class InlineStylesHead extends Head {
  getCssLinks(files) {
    return files.sharedFiles
      .filter((file) => /\.css$/.test(file))
      .filter((file) => fs.existsSync(path.join(process.cwd(), '.next', file)))
      .map((file) => (
        <style
          key={file}
          nonce={this.props.nonce}
          data-href={`${this.context.assetPrefix}/_next/${file}`}
          dangerouslySetInnerHTML={{
            __html: fs.readFileSync(
              path.join(process.cwd(), '.next', file),
              'utf-8'
            ),
          }}
        />
      ))
  }
}

export default class Document extends NextDocument {
  static async getInitialProps(ctx) {
    const initialProps = await NextDocument.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html lang="en">
        <InlineStylesHead>
          <link
            rel="preload"
            href="/fonts/Inter-roman.var-latin.woff2?3.13"
            as="font"
            type="font/woff2"
            crossOrigin="true"
          />
        </InlineStylesHead>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}


================================================
FILE: src/pages/building-react-and-vue-support-for-tailwind-ui/index.mdx
================================================
import { adamwathan } from '@/authors'
import stackedListExample from './stacked-list-example.png'

export const meta = {
  private: true,
  title: 'Building React + Vue support for Tailwind UI',
  description: `Hey! We're getting really close to releasing React + Vue support for Tailwind UI, so I thought it would be interesting to share some of the behind-the-scenes efforts that have gone into even making it possible.`,
  date: '2021-04-12T19:45:00.0Z',
  authors: [adamwathan],
}

Hey! We're getting _really_ close to releasing React + Vue support for Tailwind UI, so I thought it would be interesting to share some of the behind-the-scenes efforts that have gone into even making it possible.

Grab some popcorn...

## The Backstory

From the day we started working on Tailwind UI somewhere in mid-2019 I knew that ultimately it would be 10x more valuable to people if they could grab fully interactive examples built using their favorite JS framework. Trying to make that happen for the first release was way too ambitious though, so we had to figure out how to get there one step at a time.

We decided to focus on vanilla HTML first because it's totally universal, and even if something like JSX would be more helpful for some people, there are lots of existing tools out there for converting HTML to JSX that people could lean on already.

We also made the hard trade-off not to provide any JS for interactions like toggling a responsive menu or opening and closing a modal dialog in the first version. I felt like anything we provided would just do more harm than good, because there's no one JS framework that makes up the majority of the Tailwind user base. If we catered to React developers, we'd be making it harder to use for the 70% of people _not_ using React. If we catered to Vue developers, we'd be making it harder for the 70% of people _not_ using Vue. If we tried to write it in custom vanilla JS, well we'd be making it harder for literally everyone _(seriously do you have any idea how much code it takes to build a robust enter/leave transition system from scratch in JS?)_

So instead I just documented the different states using comments in the HTML, and left it to the end user to wire it up with their favorite JS framework. I know a lot of people love that about [Bulma](https://bulma.io), and I think it was a great approach for us to start with as well.

But once we felt like Tailwind UI was pretty fleshed out with hundreds of great examples, we decided it was time to tackle the JS problem and see what we could do.

## What should it even be?

As an abstract concept adding "JavaScript support" to Tailwind UI sounds straightforward, but when you dig in to the details it is _not_. There are _so_ many decisions to make about what to even build, and so many trade-offs you have to consider when trying to make something useful for as many people as possible.

I tossed the whole concept around in the back of my head for a full year while working on Tailwind UI before I actually had a plan I was happy with. Ultimately, these are the core values I decided on when designing a solution:

1. **The promise of Tailwind UI is that it's just a code snippet** — it's easy to customize and adapt by directly editing the code. Any JS examples we provide need to respect this foundational idea.
2. **The JS needs to be updateable**. Unlike the markup which we expect people to just totally own and edit to their heart's content, the JS needs to come from `node_modules` _somehow_, because building these things right is hard, there are going to be bugs, and we want to be able to fix them for people without asking them to copy a new code snippet. On top of that, we don't want people to have to carefully transport 200 lines of JS they didn't write around their codebase, and constantly worry about accidentally breaking some small implementation detail by mistake.
3. **It just has to be better than vanilla HTML**. At the end of the day, the most important thing is that we make the existing experience _better_ for people using the JS frameworks we decide to add support for first. Any time I found myself frustrated by two competing trade-offs that made it hard to make something _perfect_, asking myself "is this still strictly better and in no ways worse for framework X users than vanilla HTML?" provided a lot of clarity.

The other thing that was really important to me is that none of the underlying JS stuff was proprietary or Tailwind UI-specific. To me, Tailwind UI is not a UI kit like Ant Design or Material UI — those are great projects but it's not what I wanted to build.

To me, Tailwind UI is a collection of _blueprints_, showing you how to build awesome stuff using tools that are _already available_ to you. If you want to use things exactly as they come off the shelf you totally can and you'll get great results. But you should also be able to use Tailwind UI as a helpful starting point, tweak it to the nines, and end up with something that feels uniquely _yours_, even if we gave you a boost at the beginning.

So before we could add JavaScript support to Tailwind UI, we needed to build some tools.

## Building Headless UI

Years ago I remember seeing Kent C. Dodds' [downshift](https://www.downshift-js.com) library and thinking _"man, this is a cool concept — all of the complex behavior is tucked away in the library, but all of the actual markup and styling is left to the user"_.

This sort of approach is the perfect fit for Tailwind philosophically, because the entire goal of Tailwind is to help you build totally custom designs more quickly. Tailwind + a library of JS components that abstract away all of the keyboard navigation and accessibility logic without including any design opinions would be such a powerful combo — it would let teams building totally custom UIs move almost as fast as teams who were content to use hard-to-customize, opinionated frameworks.

We looked to see if there were any other tools out there solving these same problems, and while there were a few awesome projects in the space ([Reach UI](https://reach.tech) and [Reakit](https://reakit.io) especially at the time, and [react-aria](https://react-spectrum.adobe.com/react-aria/) since starting on our own library, phenomenal work by all those folks), ultimately we decided that something so important for our company would be best to build and control ourselves.

There were two big reasons we ended up starting our own project:

1. **We wanted the APIs to work well with a class-based styling solution like Tailwind.** A lot of the other tools out there expected you to write custom CSS to target the different bits of each component, which is very different than the workflow you use to style things with Tailwind. We wanted to design something that was very class-friendly.
2. **We wanted to support multiple frameworks using a consistent API.** There are React libraries, Vue libraries, Angular libraries, and others, but each one is different, designed by different people with different tastes. We wanted something that would be as consistent as possible from framework to framework, so that the framework-specific examples in Tailwind UI wouldn't be radically different from each other.

I was really excited about what we were going to end up with at the end, but holy crap this was going to be a lot of work.

### Getting the ball rolling

We decided to call this project "Headless UI" and in August of last year [Robin Malfait](https://twitter.com/malfaitrobin) joined the team to work on it full-time, pretty much exclusively.

The very first thing he worked on was a `Transition` component for React that would allow you to add enter/leave animations to elements, entirely using classes, and was very inspired by the [`<transition>`](https://v3.vuejs.org/guide/transitions-enterleave.html#enter-leave-transitions) component in Vue:

```jsx
<Transition
  show={isOpen}
  enter="transition-opacity duration-75"
  enterFrom="opacity-0"
  enterTo="opacity-100"
  leave="transition-opacity duration-150"
  leaveFrom="opacity-100"
  leaveTo="opacity-0"
>
  I will fade in and out
</Transition>
```

This is a great example of what I meant earlier when I said we really wanted to design components that were "class-friendly". This component makes it really easy to style your enter/leave transitions with regular old Tailwind utility classes, so it feels just like styling anything else in your app. It's also _not_ coupled to Tailwind in any way though, and you can use whatever classes you want!

We published the [first public release](https://blog.tailwindcss.com/headless-ui-unstyled-accessible-ui-components) in October, and it included React and Vue libraries with the first three components:

- Menu Button (or dropdown)
- Listbox (or custom select)
- Switch (or toggle)

We landed on a set of APIs that used "compound components" to abstract away all of the complexity while communicating with each other via [context](https://reactjs.org/docs/context.html) (or [provide/inject](https://v3.vuejs.org/api/composition-api.html#provide-inject) in Vue).

Here's what a custom dropdown looks like in React:

```jsx
import { Menu } from '@headlessui/react'

function MyDropdown() {
  return (
    <Menu as="div" className="relative">
      <Menu.Button className="px-4 py-2 rounded bg-blue-600 text-white ...">Options</Menu.Button>
      <Menu.Items className="absolute mt-1 right-0">
        <Menu.Item>
          {({ active }) => (
            <a className={`${active && 'bg-blue-500 text-white'} ...`} href="/account-settings">
              Account settings
            </a>
          )}
        </Menu.Item>
        <Menu.Item>
          {({ active }) => (
            <a className={`${active && 'bg-blue-500 text-white'} ...`} href="/documentation">
              Documentation
            </a>
          )}
        </Menu.Item>
        <Menu.Item disabled>
          <span className="opacity-75 ...">Invite a friend (coming soon!)</span>
        </Menu.Item>
      </Menu.Items>
    </Menu>
  )
}
```

You'll notice that to do things like style the "active" dropdown item, we use a [render prop](https://reactjs.org/docs/render-props.html) (or a [scoped slot](https://v3.vuejs.org/guide/component-slots.html#scoped-slots) in Vue):

```jsx
<Menu.Item>
  {({ active }) => (
    <a className={`${active && 'bg-blue-500 text-white'} ...`} href="/documentation">
      Documentation
    </a>
  )}
</Menu.Item>
```

Render props aren't as common as they used to be because hooks have replaced the need for them in many situations. But for this sort of problem where you need access to internal state that's managed by the component you're consuming, they are still the right (only?) solution, and very elegant.

### Designing the right components

After releasing the first version of Headless UI in October, we buckled down for a couple of months to release [Tailwind CSS v2.0](https://blog.tailwindcss.com/tailwindcss-v2), and then spent the last month of the year focused on bug fixes and lots of project house keeping before taking a break for the holidays.

When we came back, we buckled down hard to get to work on actually adding React + Vue support to Tailwind UI itself, and the first thing we needed to was audit all of the interactive behavior we needed for the examples in Tailwind UI and figure out what Headless UI abstractions we needed to design.

This was actually a pretty interesting and challenging job, because it's really not always obvious how a certain design-specific interaction should map to an established UI pattern that has known accessibility expectations.

Some are obvious:

- A modal dialog should be a [dialog](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/dialog_role)
- A toggle should be a [switch](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Switch_role)
- A dropdown should be a [menu](https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html) _(well, sometimes...)_

But some are a lot trickier. For example, what about mobile menus, the kind of thing you open with a hamburger button?

_If it opens kinda like a popup, is that a menu like a dropdown?_

_What if it slides in from the side of the screen?_

_What if it just opens in place and pushes the rest of the page further down?_

We worked through questions like this regularly, and landing on good solutions took a lot of research and experimentation. We're lucky to have [David Luhr](https://twitter.com/david_luhr) on the team who has specialized in accessibility for a long time, and with his help we were able to feel really good about the solutions we landed on.

Here's what we decided we needed in order to support the patterns that already existed in Tailwind UI:

- **Menu Button**. Used for dropdown menus that only contain links or buttons, like a little actions menu at the end of a table row.
- **Listbox**. For custom `select` implementations where you want to include extra stuff in the `option` elements. For example a country picker where you put a flag next to each country.
- **Switch**. For custom toggle switches that behave like checkboxes.
- **Disclosure**. For showing/hiding content in place. Think like collapsable FAQ questions. Also useful for bigger chunks of UI too though, like a mobile menu that opens in place and pushes the rest of the page down.
- **Dialog**. For, well, modal dialogs! But also for mobile navigation that slides out from the side of the page, and other "take-over"-style UIs, even if they don't look like a traditional panel-centered-in-the-screen modal.
- **Popover**. For panels that pop up on top of the page when you click a button. This is useful for menus where you need lots of custom content that would violate the strictness of regular `role="menu"` menu buttons. We use these for some mobile menus, flyout menus in navigation bars, and other interesting places too. It's kind of like a menu/disclosure hybrid.
- **Radio Group**. For custom radio selection UIs, like where you want a set of clickable cards instead of a boring little radio circle.

We ran into tons of challenges building this stuff, especially around complex stuff like focus management, and _especially_ around nested focus management.

Imaging you have a modal that opens, and inside that modal there's a dropdown. You open the modal, then open the dropdown, and hit escape. What happens? Well the dropdown should close right, but the modal should stay open.

I guarantee 99% of modals on the internet would close too in this case, even though they aren't supposed to. But not ours — ours works!

We _(well mostly Robin)_ spent _months_ working on little details like this to make everything as bullet-proof as possible, and while I'm sure there have to be bugs hiding in there still somewhere, where we ended up feels _so_ rock solid compared to almost every UI you encounter day-to-day on the web.

We still have a lot of new patterns we want to add to Headless UI like tabs, accordions, maybe even _gulp_ a datepicker, and we're looking forward to exploring other frameworks in the future (Alpine.js is next on our list), but we're super proud to call what we're releasing this week Headless UI v1.0 and commit to a stable API going forward.

We think you're gonna love it. _&lt;/TimCook&gt;_

## Just enough abstraction

With the Headless UI stuff figured out, the next big problem was figuring out _exactly_ what a React or Vue version of an existing Tailwind UI example should look like.

The examples in Tailwind UI are pure HTML snippets — you find something you like, copy the HTML into your project, then tweak it as much you like, chop it up into individual components, whatever you want. We don't make any assumptions about how you're going to use it, what elements you're going to keep or delete, or how you want to abstract away any duplication with your preferred tools.

This is an easy decision when working with pure HTML — what other choice do you really even have? But when offering framework-specific examples, it gets a lot trickier to know exactly what to provide.

The biggest question was how hard should we try to remove any duplication, and what are the right approaches to doing so?

Both React and Vue are _component_ frameworks, and the way you reuse code in your projects is by extracting bits of UI into components that you can use over and over again.

The challenge is that creating components like that is always _very_ project specific. Take this list component for example:

<img src={stackedListExample} alt="Stacked list component example from Tailwind UI" />

Fully componentized in a real app, the final code might look something like this:

```jsx
<TeamList>
  {projectMembers.map(member => (
    <TeamList.Item teamMember={member}>
  ))}
</TeamList>
```

It looks super clean sure, but it's forcing a lot of opinions on you.

For example, it assumes the items are team members. What if you're building an invoicing app and you want to use this pattern for a list of clients instead? Hell, you might be using this for a sports betting app and these should be baseball teams, not even people!

It also makes assumptions about the shape of a `member` object. It would have to encode that it's pulling out a `name` and an `email` property, even though your data might be different.

The other issue is that in frameworks like Vue, you can only have one component per file. This means copying an example that was made up of 4-5 subcomponents would mean you have to copy 4-5 different snippets, create files for each one, and link them all together with the correct names/paths.

To me, something about doing all of this for people felt like going too far, at least for the problem we're trying to solve today. When everything is super broken up like that with predefined prop APIs and deliberately chosen component names, **it feels like you aren't supposed to change it anymore**. What I love about Tailwind UI is that clicking the "code" tab feels like opening up some complex piece of electronics and seeing all of the circuitry right there in front of you. It's a learning opportunity, and you can read the markup and class names and understand how it all works together.

I wrestled with it for a long time, but ultimately decided that right now we were trying to solve two main problems:

1. **Give people code using the syntax they actually need**, like giving React users JSX instead of HTML so they don't have to manually convert things like `for` to `htmlFor`.
2. **Make the interactive elements work out of the box**, so dropdowns, mobile menus, toggles, and everything else was ready to go, instead of having to write all of that boilerplate JS yourself.

I decided that the right solution was to focus on solving those problems, and be careful not to do anything that would turn Tailwind UI into a different product.

So this is what's different when you look at a React or Vue example compared to the vanilla HTML version:

1. **Each framework example uses the right syntax** — React examples use JSX, and Vue examples are provided in the single-file component syntax.
2. **Transitions are real now** — instead of comments telling you what classes to add at each phase of a transition, the transition is just there, using either a Headless UI transition component or Vue's native transition component.
3. **Interactive elements are handled by Headless UI** — you'll see a few imports in any example that requires JS where we pull in the required Headless UI components and then those are used directly in the markup.
4. **Any repeated chunks of markup have been converted into basic loops** — any data-driven loop stuff (like lists of people, or navigation items) are extracted into simple variables right there in the example to reduce duplication but still keep everything together in one place. In your own projects, you'd swap this out with data from an API or database or whatever, but we keep the examples simple and don't make any assumptions for you.
5. **Icons are pulled in from the Heroicons library**. Instead of inlining the SVG directly whenever an icon is used, we pull them in from our React/Vue icon libraries instead to keep the markup simpler.

Here's an example of what it actually looks like:

```jsx
import { Menu, Transition } from '@headlessui/react'
import { DotsVerticalIcon } from '@heroicons/react/solid'
import { Fragment } from 'react'

const people = [
  {
    name: 'Calvin Hawkins',
    email: 'calvin.hawkins@example.com',
    image:
      'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
  },
  {
    name: 'Kristen Ramos',
    email: 'kristen.ramos@example.com',
    image:
      'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
  },
  {
    name: 'Ted Fox',
    email: 'ted.fox@example.com',
    image:
      'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
  },
]

export default function Example() {
  return (
    <ul className="divide-y divide-gray-200">
      {people.map((person) => (
        <li key={person.email} className="py-4 flex">
          <img className="h-10 w-10 rounded-full" src={person.image} alt="" />
          <div className="ml-3">
            <p className="text-sm font-medium text-gray-900">{person.name}</p>
            <p className="text-sm text-gray-500">{person.email}</p>
          </div>
          <Menu as="div" className="ml-3 relative inline-block text-left">
            {({ open }) => (
              <>
                <div>
                  <Menu.Button className="bg-gray-100 rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500">
                    <span className="sr-only">Open options</span>
                    <DotsVerticalIcon className="h-5 w-5" aria-hidden="true" />
                  </Menu.Button>
                </div>

                <Transition
                  show={open}
                  as={Fragment}
                  enter="transition ease-out duration-100"
                  enterFrom="transform opacity-0 scale-95"
                  enterTo="transform opacity-100 scale-100"
                  leave="transition ease-in duration-75"
                  leaveFrom="transform opacity-100 scale-100"
                  leaveTo="transform opacity-0 scale-95"
                >
                  <Menu.Items
                    static
                    className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
                  >
                    <div className="py-1">
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            href="#"
                            className={classNames(
                              active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
                              'block px-4 py-2 text-sm'
                            )}
                          >
                            View details
                          </a>
                        )}
                      </Menu.Item>
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            href="#"
                            className={classNames(
                              active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
                              'block px-4 py-2 text-sm'
                            )}
                          >
                            Send message
                          </a>
                        )}
                      </Menu.Item>
                    </div>
                  </Menu.Items>
                </Transition>
              </>
            )}
          </Menu>
        </li>
      ))}
    </ul>
  )
}
```

It's still a single example where you can see everything that's going on at once, and you can cut it up however makes the most sense for your project. You get to define your own prop APIs to meet your own needs, name things however makes the most sense for your domain, and fetch your data in whatever way works best with the other technologies you work with.

## The machine that makes it work

So that's how it all works from a customer's perspective, but if you're curious how we actually built this stuff internally, it's pretty interesting and worth talking about.

Tailwind UI is like 450 examples or something now, and converting all of that stuff to React/Vue by hand would have been absolute torture, and impossible to maintain in the long-term. So we needed some way to automate it.

If you're anything like me, the entire idea of automatically generating this stuff in different formats might make you cringe. For me at least, my gut reaction is just _"well there goes the human touch — it's just going to feel like machine-generated garbage now"_, and of course that is not acceptable to me at all — I want to be proud of the stuff we release, not feel like we had to make really ugly compromises.

So however we did this, the output had to live up to our standards. This meant we were gonna have to build a system to do this ourselves, from scratch.

For the first 2 months of the year, [Brad](https://twitter.com/bradlc) spent all of his time building a custom authoring chain specifically for Tailwind UI components that could take our HTML and turn it into React code that looked like it was hand-written by a person.

Here's how it works — instead of authoring our examples in vanilla HTML, we author them in a sort of custom flavor of HTML full of custom elements that we ultimately transform to vanilla HTML using [PostHTML](https://github.com/posthtml/posthtml).

Here's what one of our dropdown examples looks like in our internal authoring format:

```html
<x-menu as="div" id="options-menu" class="relative inline-block text-left">
  <div>
    <x-menu-button
      class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
    >
      Options
      <x-heroicon type="solid" name="chevron-down" class="-mr-1 ml-2 h-5 w-5" />
    </x-menu-button>
  </div>

  <x-transition
    as="x-fragment"
    enter="transition ease-out duration-100"
    enter-start="transform opacity-0 scale-95"
    enter-end="transform opacity-100 scale-100"
    leave="transition ease-in duration-75"
    leave-start="transform opacity-100 scale-100"
    leave-end="transform opacity-0 scale-95"
  >
    <x-menu-items
      class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
    >
      <div class="py-1">
        <x-menu-item>
          <a
            href="#"
            class="block px-4 py-2 text-sm"
            x-active-class="bg-gray-100 text-gray-900"
            x-not-active-class="text-gray-700"
          >
            Account settings
          </a>
        </x-menu-item>
        <x-menu-item>
          <a
            href="#"
            class="block px-4 py-2 text-sm"
            x-active-class="bg-gray-100 text-gray-900"
            x-not-active-class="text-gray-700"
          >
            Support
          </a>
        </x-menu-item>
        <x-menu-item>
          <a
            href="#"
            class="block px-4 py-2 text-sm"
            x-active-class="bg-gray-100 text-gray-900"
            x-not-active-class="text-gray-700"
          >
            License
          </a>
        </x-menu-item>
      </div>
    </x-menu-items>
  </x-transition>
</x-menu>
```

You can probably already see why authoring things this way makes it so much easier to convert to something like React or Vue than just writing the HTML by hand.

We crawl this document as an AST, and actually transform it into _four_ formats:

1. The vanilla HTML you get when you copy the snippet.
2. The HTML that gets injected into the preview pane, where we use some very quick and dirty Alpine.js to demo the different interactions in the example.
3. The React snippet for you to copy.
4. The Vue snippet for you to copy.

The key to getting sensible output is really just having total control of the input format. It's still hard work, but when you can encode the _intent_ of each example into a custom input format, converting that to another format turns out _so_ much better than trying to write something that can convert arbitrary jQuery to React or something.

There's still some dark magic in there with regular expressions and all of the other usual suspects, but ultimately by keeping things as declarative as possible and hiding the real complexity inside of Headless UI, we're mostly just transforming markup which is a lot more restricted than regular code.

## When's it coming out?

React and Vue support for Tailwind UI is going to be available to everyone on Wednesday April 14th — two days from now! It's a completely free update for all customers, you'll just see a new little dropdown appear in the UI for changing the snippet language and you'll be ready to go.

We'll also be releasing Headless UI v1.0 on the same day _(of course, since how else would this Tailwind UI stuff even work)_ along with a brand new documentation site, so even if you're not a Tailwind UI customer, there's gonna be lots of new free open-source goodies for you to play with.

Thanks as always to everyone supporting our work on this stuff — it's seriously a gift to get to work on tools like this for other developers every day and it brings us a ton of fulfillment to see people benefiting from what we build.

Hope you enjoy the stuff!

– Adam


================================================
FILE: src/pages/building-the-tailwind-blog/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Building the Tailwind Blog with Next.js',
  description: `One of the things we believe as a team is that everything we make should be sealed with a blog post. Forcing ourselves to write up a short announcement post for every project we work on acts as a built-in quality check, making sure that we never call a project "done" until we feel comfortable telling the world it's out there. The problem was that up until today, we didn't actually have anywhere to publish those posts!`,
  date: '2020-06-30T18:05:31Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindcss/tailwindcss/discussions/1987',
}

One of the things we believe as a team is that everything we make should be [sealed with a blog post](https://public.3.basecamp.com/p/toAcDMxu8Fvq2yMfd2azTuaV). Forcing ourselves to write up a short announcement post for every project we work on acts as a built-in quality check, making sure that we never call a project "done" until we feel comfortable telling the world it's out there.

The problem was that up until today, we didn't actually have anywhere to publish those posts!

<!--more-->

## Choosing a platform

We're a team of developers so naturally there was no way we could convince ourselves to use something off-the-shelf, and opted to build something simple and custom with [Next.js](http://nextjs.org).

There are a lot of things to like about Next.js, but the primary reason we decided to use it is that it has great support for [MDX](https://mdxjs.com/), which is the format we wanted to use to author our posts.

```md
# My first MDX post

MDX is a really cool authoring format because it lets
you embed React components right in your markdown:

<MyComponent myProp={5} />

How cool is that?
```

MDX is really interesting because unlike regular Markdown, you can embed live React components directly in your content. This is exciting because it unlocks a lot of opportunities in how you communicate ideas in your writing. Instead of relying only on images, videos, or code blocks, you can build interactive demos and stick them directly between two paragraphs of content, without throwing away the ergonomics of authoring in Markdown.

We're planning to do a redesign and rebuild of the Tailwind CSS documentation site later this year and being able to embed interactive components makes a huge difference in our ability to teach how the framework works, so using our little blog site as a test project made a lot of sense.

## Organizing our content

We started by writing posts as simple MDX documents that lived directly in the `pages` directory. Eventually though we realized that just about every post would also have associated assets, for example an Open Graph image at the bare minimum.

Having to store those in another folder felt a bit sloppy, so we decided instead to give every post its own folder in the `pages` directory, and put the post content in an `index.mdx` file.

```
public/
src/
├── components/
├── css/
├── img/
└── pages/
    ├── building-the-tailwindcss-blog/
    │   ├── index.mdx
    │   └── card.jpeg
    ├── introducing-linting-for-tailwindcss-intellisense/
    │   ├── index.mdx
    │   ├── css.png
    │   ├── html.png
    │   └── card.jpeg
    ├── _app.js
    ├── _document.js
    └── index.js
next.config.js
package.json
postcss.config.js
README.md
tailwind.config.js
```

This let us co-locate any assets for that post in the same folder, and leverage webpack's [file-loader](https://github.com/tailwindcss/blog/blob/59bd38f2f423f133c0b6157a925ec2875ce880af/next.config.js#L28-L39) to import those assets directly into the post.

## Metadata

We store metadata about each post in a `meta` object that we export at the top of each MDX file:

```js
import { bradlc } from '@/authors'
import openGraphImage from './card.jpeg'

export const meta = {
  title: 'Introducing linting for Tailwind CSS IntelliSense',
  description: `Today we’re releasing a new version of the Tailwind CSS IntelliSense extension for Visual Studio Code that adds Tailwind-specific linting to both your CSS and your markup.`,
  date: '2020-06-23T18:52:03Z',
  authors: [bradlc],
  image: openGraphImage,
  discussion: 'https://github.com/tailwindcss/tailwindcss/discussions/1956',
}

// Post content goes here
```

This is where we define the post title (used for the actual `h1` on the post page and the page title), the description (for Open Graph previews), the publish date, the authors, the Open Graph image, and a link to the GitHub Discussions thread for the post.

We store all of our authors data in a separate file that just contains each team member's name, Twitter handle, and avatar.

```js
import adamwathanAvatar from './img/adamwathan.jpg'
import bradlcAvatar from './img/bradlc.jpg'
import steveschogerAvatar from './img/steveschoger.jpg'

export const adamwathan = {
  name: 'Adam Wathan',
  twitter: '@adamwathan',
  avatar: adamwathanAvatar,
}

export const bradlc = {
  name: 'Brad Cornes',
  twitter: '@bradlc',
  avatar: bradlcAvatar,
}

export const steveschoger = {
  name: 'Steve Schoger',
  twitter: '@steveschoger',
  avatar: steveschogerAvatar,
}
```

The nice thing about actually importing the author object into a post instead of connecting it through some sort of identifier is that we can easily add an author inline if we wanted to:

```js
export const meta = {
  title: 'An example of a guest post by someone not on the team',
  authors: [
    {
      name: 'Simon Vrachliotis',
      twitter: '@simonswiss',
      avatar: 'https://pbs.twimg.com/profile_images/1160929863/n510426211_274341_6220_400x400.jpg',
    },
  ],
  // ...
}
```

This makes it easy for us to keep author information in sync by giving it a central source of truth, but doesn't give up any flexibility.

## Displaying post previews

We wanted to display previews for each post on the homepage, and this turned out to be a surprisingly challenging problem.

Essentially what we wanted to be able to do was use the `getStaticProps` feature of Next.js to get a list of all the posts at build-time, extract the information we need, and pass that in to the actual page component to render.

The challenge is that we wanted to do this without actually importing every single page, because that would mean that our bundle for the homepage would contain every single blog post for the entire site, leading to a much bigger bundle than necessary. Maybe not a big deal right now when we only have a couple of posts, but once you're up to dozens or hundreds of posts that's a lot of wasted bytes.

We tried a few different approaches but the one we settled on was using webpack's [resourceQuery](https://webpack.js.org/configuration/module/#ruleresourcequery) feature combined with a couple of custom loaders to make it possible to load each blog post in two formats:

1. The entire post, used for post pages.
2. The post preview, where we load the minimum data needed for the homepage.

The way we set it up, any time we add a `?preview` query to the end of an import for an individual post, we get back a much smaller version of that post that just includes the metadata and the preview excerpt, rather than the entire post content.

Here's a snippet of what that custom loader looks like:

```js
{
  resourceQuery: /preview/,
  use: [
    ...mdx,
    createLoader(function (src) {
      if (src.includes('<!--​more​-->')) {
        const [preview] = src.split('<!--​more​-->')
        return this.callback(null, preview)
      }

      const [preview] = src.split('<!--​/excerpt​-->')
      return this.callback(null, preview.replace('<!--​excerpt​-->', ''))
    }),
  ],
},
```

It lets us define the excerpt for each post either by sticking `<!--​more-->` after the intro paragraph, or by wrapping the excerpt in a pair of `<!--​excerpt-->` and `<!--​/excerpt-->` tags, allowing us to write an excerpt that's completely independent from the post content.

```
export const meta = {
  // ...
}

This is the beginning of the post, and what we'd like to
show on the homepage.

<!--​more-->

Anything after that is not included in the bundle unless
you are actually viewing that post.
```

Solving this problem in an elegant way was pretty challenging, but ultimately it was cool to come up with a solution that let us keep everything in one file instead of using a separate file for the preview and the actual post content.

## Generating next/previous post links

The last challenge we had when building this simple site was being able to include links to the next and previous post whenever you're viewing an individual post.

At its core, what we needed to do was load up all of the posts (ideally at build-time), find the current post in that list, then grab the post that came before and the post that came after so we could pass those through to the page component as props.

This ended up being harder than we expected, because it turns out that MDX doesn't currently support `getStaticProps` the way you'd normally use it. You can't actually export it directly from your MDX files, instead you have to store your code in a separate file and re-export it from there.

We didn't want to load this extra code when just importing our post _previews_ on the homepage, and we also didn't want to have to repeat this code in every single post, so we decided to prepend this export to the beginning of each post using another custom loader:

```js
{
  use: [
    ...mdx,
    createLoader(function (src) {
      const content = [
        'import Post from "@/components/Post"',
        'export { getStaticProps } from "@/getStaticProps"',
        src,
        'export default (props) => <Post meta={meta} {...props} />',
      ].join('\n')

      if (content.includes('<!--​more-->')) {
        return this.callback(null, content.split('<!--​more-->').join('\n'))
      }

      return this.callback(null, content.replace(/<!--​excerpt-->.*<!--\/excerpt-->/s, ''))
    }),
  ],
}
```

We also needed to use this custom loader to actually pass those static props to our `Post` component, so we appended that extra export you see above as well.

This wasn't the only issue though. It turns out `getStaticProps` doesn't give you any information about the current page being rendered, so we had no way of knowing what post we were looking at when trying to determine the next and previous posts. I suspect this is solvable, but due to time constraints we opted to do more of that work on the client and less at build time, so we could actually see what the current route was when trying to figure out which links we needed.

We load up all of the posts in `getStaticProps`, and map them to very lightweight objects that just contain the URL for the post, and the post title:

```js
import getAllPostPreviews from '@/getAllPostPreviews'

export async function getStaticProps() {
  return {
    props: {
      posts: getAllPostPreviews().map((post) => ({
        title: post.module.meta.title,
        link: post.link.substr(1),
      })),
    },
  }
}
```

Then in our actual `Post` layout component, we use the current route to determine the next and previous posts:

```js
export default function Post({ meta, children, posts }) {
  const router = useRouter()
  const postIndex = posts.findIndex((post) => post.link === router.pathname)
  const previous = posts[postIndex + 1]
  const next = posts[postIndex - 1]

  // ...
}
```

This works well enough for now, but again long-term I'd like to figure out a simpler solution that lets us load only the next and previous posts in `getStaticProps` instead of the entire thing.

There's an interesting library by Hashicorp designed to make it possible to treat MDX files like a data source called [Next MDX Remote](https://github.com/hashicorp/next-mdx-remote) that we will probably explore in the future. It should let us switch to dynamic slug-based routing which would give us access to the current pathname in `getStaticProps` and give us a lot more power.

## Wrapping up

Overall, building this little site with Next.js was a fun learning experience. I'm always surprised at how complicated seemingly simple things end up being with a lot of these tools, but I'm very bullish on the future of Next.js and looking forward to building the next iteration of [tailwindcss.com](https://tailwindcss.com) with it in the months to come.

If you're interested in checking out the codebase for this blog or even submitting a pull request to simplify any of the things I mentioned above, [check out the repository on GitHub](https://github.com/tailwindcss/blog).


================================================
FILE: src/pages/designing-tailwind-ui-ecommerce/index.mdx
================================================
import { adamwathan } from '@/authors'
import card from './card.jpg'

export const meta = {
  private: true,
  title: 'Designing Tailwind UI Ecommerce',
  description: `Hey! We've been working on this new Tailwind UI Ecommerce kit for months now and are finally closing in on the finish line so I thought I'd write up a bit about the process and give you an update on where things are at.`,
  date: '2021-08-07T12:45:00.0Z',
  authors: [adamwathan],
  image: card,
}

Hey! We've been working on this new Tailwind UI Ecommerce kit for months now and are finally closing in on the finish line so I thought I'd write up a bit about the process and give you an update on where things are at.

Designing and building a big component kit like this is a _huge_ amount of work. If you weren't following along when Steve and I started working on Tailwind UI in the first place, it took about 10 months before we had something we felt good about releasing. We designed dozens of components only to scrap all of them and start from scratch at least three separate times. _Oof._

We've learned a lot since then and have really refined our process, and it's made putting together these ecommerce ideas go a lot smoother.

---

**The first step was to research and catalog**. We started by studying as many ecommerce sites as we could possibly find and cataloging every UI pattern we could pick out, like:

- Product overviews
- Product lists
- Product feature details
- Category previews
- Checkout forms
- Shopping carts
- Category filters
- Customer reviews
- Order history
- Order details
- Category mega menus
- Product quickviews
- Promo sections

We scoured dozens of different types of stores as part of this research, including clothing stores like [Everlane](https://www.everlane.com/), shoe stores like [Allbirds](https://www.allbirds.com/), office accessory makers like [Grovemade](https://grovemade.com/), mattress companies like [Casper](https://casper.com/), audio plugin developers like [Toontrack](https://www.toontrack.com/), and tons more. We tried not to focus on any one specific _type_ of store, and I think that really helped in identifying which patterns are truly universal for these types of sites, and how different ideas fit into those patterns.

Some of them were pretty hard to identify, especially because there's quite a bit of overlap between the sort of patterns you see in an ecommerce site and in a more brochure-style marketing website.

For example, this section isn't really that different from a hero section you might see on a marketing site:

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/adidas-hero.jpg)

So should the ecommerce kit have "Hero Sections" too, like the marketing kit? And if so what makes them different — why have two duplicate categories?

It took a lot of research and thinking before we eventually noticed that in ecommerce sites, the "hero" is almost _never_ a real hero. It's never something with like a summary of the whole brand or anything, like "Handmade office accessories for the modern knowledge worker", it's always some timely promotional thing like "Get 50% off until the end of the summer" or "Our fall collection just dropped, explore it now".

We also noticed that these "hero" sections were almost always designs that could very easily be moved to the middle of the page — they were never really true "header" content. This is when we came up with the concept of a "promo section", which is like a big banner that promotes a sale or a new product and _could_ be used as a hero, but could also be mixed in with other page content as well.

There were lots of little eureka moments like this that don't seem like much when you see the finished result but man, it is hard work to figure this stuff out. Organizing and categorizing all of these ideas was almost as much work as designing them.

---

**Next was designing full page examples**. If there's one thing we learned from the initial work on the Application UI and Marketing packages, it's that the components you extract are a lot better when designed in the context of a complete page.

For the first set of Tailwind UI stuff, we did a lot of designing in isolation, just thinking through a nice pagination design for example. But what we found is that when we started putting all that stuff together into real UIs, lots of little things felt off.

Sometimes it was the font size being a little too big compared to other elements in the design, maybe not using enough whitespace, or giving an element too much contrast, making it stand out against the rest of the page when it was supposed to feel like secondary content. When we reworked our approach to always _extract_ smaller examples from bigger examples, everything really started to click a lot better.

So for the ecommerce package, we designed _everything_ as page examples from the very beginning. In total, we've designed around 50 complete pages so far, including home pages, category listings, product pages, checkout forms, and tons more.

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/50-pages.jpg)

We'd reuse a few things across pages to move quickly (like footer or navbar ideas), but generally we tried to make every core element of every page unique, to generate as many ideas as possible.

---

**Then we built the pages.** A lot of folks assume we have this like really clean design hand-off process where we create these bullet-proof, perfect Figma designs, then implement them exactly as-is in their perfect form but that's totally not true at all.

What happens instead is we take the full page designs that are in _pretty good_ shape, build them as best we can, then we review the finished designs together in the browser to make adjustments. Usually there are a _lot_ of little changes that happen, especially stuff related to little spacing details that are just easier to judge in the browser than they are in a design file. Sometimes though we make really drastic changes, like totally replacing part of the design with something else if we decide it just doesn't look as good as we hoped, or if it would make the code extremely complicated to match the design exactly but for little or no benefit over a slightly different design.

Here's an example of a design that changed a _lot_ during the building process — Figma on the left, final HTML version on the right:

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/figma-vs-html.jpg)

Definitely think everything turns out a lot better when we are open to iterating on designs like this as we go instead of just throwing them over the wall.

If you're interested in the actual process we use for writing the templates, we author them in a sort of home-grown templating language driven by PostHTML, which we ultimately use to render the components to multiple targets, like HTML, React, and Vue.

Here's what it looks like:

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/templating-sample.jpg)

You can read more about it [in this email I sent back in April](https://blog.tailwindcss.com/building-react-and-vue-support-for-tailwind-ui) if you're curious about more of the details!

---

**Next, we extracted the individual components.** Once the page designs are finalized and built, we extract the individual elements into their own templates. This is where "Product Overviews" are extracted from product pages, "Product Lists" are extracted from category pages, and "Promo Sections" are extracted from storefront home pages.

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/exploded-view.jpg)

The thing that's tough about this process is it's not really as simple as just "a page is comprised of components" — as you probably know even _components_ are comprised of components, so deciding exactly how to slice things for Tailwind UI is always tough.

Generally we err on the side of bigger components, because it's easy for someone to pull out a small piece of a bigger component if they like it and want to use it elsewhere, but it's not always easy to come up with ideas for the bigger components yourself if all you have is the tiny individual pieces.

The way I like to think about it is we're trying to give you pre-built LEGO creations, not just the bricks.

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/lego.jpeg)

It's easy to take the wheels off the truck if that's all you need — it's harder to turn the wheels, a seat, and some headlights into a nice truck from scratch though.

For example, this single "Product Overview" section could be cut up into a bunch of smaller components:

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/product-overview.jpg)

There's an image gallery, a star review widget, a color swatch picker, and a feature accordion there at a bare minimum. But I think providing it holistically like this is the better place to start — you can still grab all that stuff if you need it but as a bigger component it also serves as _inspiration_ and that's a really important ingredient in what makes Tailwind UI a cool product.

We might extract a lot of these things into smaller components as well down the road, but I definitely think this "outside-in" approach is the right way to go overall.

---

**Finally, take inventory, and repeat the whole thing.** A lesson I keep re-learning over and over again about product design is that no amount of planning will lead to the right product on the first try. You have to be keep iterating and improving — it's not a linear process.

For this ecommerce kit, we couldn't really see where the holes were in the product until we had designed and built a bunch of pages, extracted all of those components, and then assessed where we were at.

We noticed a bunch of things we were blind to at first, for example, we only had two mega menu ideas — probably because it was really easy to just not design the "open" state of any navbar when designing new pages. So we went back to the design step, and came up with a bunch of new concepts to flesh out the category.

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/mega-menu-concepts.jpg)

We also noticed that we had a disproportionate number of sort of card-based designs on off-white backgrounds, and not enough just flat simple designs which I think is a lot more common in ecommerce design. So we went back to the beginning and designed a bunch of new pages to fill this gap as well:

![](https://tailwindui.nyc3.cdn.digitaloceanspaces.com/20210806-ecommerce-update/white-backgrounds.jpg)

We're less than a week away from when we hope to release this thing and every day we are _still_ designing brand new things to fill gaps we are identifying as we get closer to the finish line. The product is getting more awesome every day though, and I think these are the best designs we've come up with for Tailwind UI period _(thank god really, getting worse at this would be depressing.)_

---

**So when is it coming out?** I don't want to make any promises but we are trying _really_ hard to get this thing out the door mid-next week. I'll definitely send out another update when we know for sure, but it's looking good right now even though we've still got a lot to wrap up.

As mentioned in an earlier update, the ecommerce kit is a separate product so it will be a paid add-on for existing customers, but if you already own the Marketing and Application UI packages, it's going to be _really_ affordable, like cheaper than taking your kids out for lunch sort of territory. I **hate** when companies give the best deals to new customers _(hello telecom industry)_ instead of rewarding the people who supported them from the beginning, and we will never be that type of business.

Thanks as always for following along — look for another update early next week, and fingers crossed we'll have this ready for release shortly after!

– Adam


================================================
FILE: src/pages/from-900-to-1-how-we-hired-robin-malfait/index.mdx
================================================
import { adamwathan } from '@/authors'
import cardImage from './card.jpg'
import robinImage from './robin.jpg'

export const meta = {
  title: 'From Nine Hundred to One: How We Hired Robin Malfait',
  description: `Back in May we published our first job posting for a full-stack developer to join our team. This is the story of how we worked through almost 900 applications, and eventually hired Robin Malfait, a talented developer from Belgium who is starting with us today.`,
  date: '2020-08-10T13:30:00.000Z',
  authors: [adamwathan],
  image: cardImage,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2153',
}

Back in May we published [our first job posting](https://jobs.tailwindui.com/full-stack-developer) to help us find a full-stack developer to join our team.

After receiving almost 900 applications and interviewing dozens of talented people, we're excited to finally share that [Robin Malfait](https://twitter.com/malfaitrobin) accepted our offer for the position and is officially part of the Tailwind Labs team as of today!

<!--more-->

<img src={robinImage} alt="" />

Robin is a talented developer from Belgium, and has been an active member of the Tailwind community for a long time. If you're a Tailwind UI customer and have ever asked a question in the `#react` channel on our Discord server, there's a 90% chance he's the helpful person who answered your question. He even built a [bookmarklet](https://gist.github.com/RobinMalfait/a90e8651196c273dfa51eec0f43e1676) to help people convert Tailwind UI components to JSX!

Robin is a seriously experienced React developer, and is joining us to help spearhead the open-source [renderless UI libraries](https://twitter.com/adamwathan/status/1265815748917813248) we are working on that will be the foundation for official React and Vue _(to start anyways)_ support in Tailwind UI.

We're super excited that he is finally starting with us today, and can't wait to watch his contributions enable people to build awesome UIs even faster and with more confidence. Welcome to the team dude!

What follows is the story of how we went about hiring for this role, and how we narrowed down the candidates from almost 900 initial applications to finally making an offer to Robin.

---

## The Job Posting

Before this role, we had only hired [Brad](https://blog.tailwindcss.com/welcoming-brad-cornes-to-the-tailwind-team) who we already knew and trusted, so we didn't need a job posting or any sort of rigorous application process.

I knew that if we wanted to get really great candidates, we had to write a compelling job posting. After about 3-4 days of working on it, this is where we ended up:

[**Read the job posting &rarr;**](https://jobs.tailwindui.com/full-stack-developer)

Here are the important things I focused on when writing it:

- Be specific about the projects the applicant would be working on after they started
- Be clear that we are a small team, so everyone has to do a bit of everything, including customer support
- Give concrete examples of projects we just finished that the applicant would have worked on if they were already at the company
- Go into detail about specific hard problems we expect to run into on the next major upcoming project, to help the applicant understand the sort of expertise that would be valuable to us
- Share concrete salary and benefit information. I would never apply for a job without a clear understanding of the salary, so why should I expect talented people to apply for our posting without it?

We got _tons_ of positive feedback about this posting, and I'm really proud of how it turned out. I think it was very applicant-centric, and I think it made a big difference in the quality of submissions we got.

## The Application Process

One thing we did a bit differently from other companies is that we didn't ask for a resume or give applicants a big list of questions to answer. All we asked for was an "application", in whatever form the person decided. It could be a cover letter, a small website, a video, a slide deck, whatever.

I decided to ask for applications this way for a few reasons:

- I just don't think resumes are that important
- I wanted to filter for people with some inherent marketing sensibilities, we're a tiny company so we need [T-shaped](https://en.wikipedia.org/wiki/T-shaped_skills) people more than we need specialists
- I wanted to filter for people who can ship things, and making the application completely free-form tells you a lot about someone's ability to take something from nothing to polished product on their own
- I wanted to find someone who talked about the stuff we were looking for without being prompted for it — finding someone who was naturally well-aligned with what we are trying to do would be a big advantage for us
- I expected a lot of applications, and I thought asking for applications this way would make it easy to filter people out who were using a shotgun approach to job-searching and not specifically interested in working with us

Even with what I think was a fairly intimidating application process, we got well over 100 applications where there was clearly a lot of time spent crafting something very specific for our posting, including Robin's of course:

[**Read Robin's application &rarr;**](https://robin-malfait-tailwind-job-application.now.sh/)

Some people did some _really_ out there and creative things in their applications _(one person even made an interactive game!)_ but Robin's stood out to us for a few reasons:

- The visual design was great. We're a very design-focused company, so having good taste in design is really important to us.
- His story about learning to program and getting into the Laravel community told me we had a rich shared history, even if we had never met.
- He took a chance and shared some strong opinions he had about component design that were _extremely_ relevant to some work we'll be doing very soon, and I agreed with what he was saying and even learned a few things.
- He shared a super interesting [open-source library](https://github.com/RobinMalfait/lazy-collections) he authored, which despite being very unknown, still had very well thought-out and complete documentation that was presented in a very well-structured way. It was clear he thinks about visual design even when authoring a markdown file.
- He shared lots of concrete ideas for projects he'd like to work on with us, and a lot of them were things I was already excited about doing.
- He capitalized the "H" in "GitHub" _(holy shit I hate when people don't do that)_.

Robin's was one of maybe 40-50 that _really_ stood out from a content perspective.

## Filtering the Applications

Dealing with almost 900 job applications is a lot of work. Over half of them we were able to discard immediately because they just provided a link to their LinkedIn profile or a generic resume, but filtering through the rest was really tough.

I've never hired anyone this way before, and at first I really felt like we needed to meet with and interview _everyone_ who submitted a quality application. As the applications poured in though, I realized this was just not practical, and that we had to put some sort of cap on it.

I decided to sort the good applications as best I could, then just slice off the top 20 and start there. It meant there were lots of great people we wouldn't talk to and that maybe we even missed out on the absolute best applicant, but the reality is that we only have so much time we can dedicate to this, and I had to believe that out of the ~20 best applications, there would certainly be _multiple_ people we wouldn't regret hiring at all, even if there was a chance that the absolute best person was somewhere in the other 30.

## The Interview Process

We started by scheduling video interviews with the top ~20 applicants, which took about 3 weeks to get through.

These were 30-45 minute calls where we had a pretty casual conversation about a few topics:

- What the person had been working on recently, and where they think their strengths are
- Why they applied for the job, and what about the role was interesting to them
- What we as a company are going to be doing over the next year or so, and digging into a few projects in detail
- Answering any questions the person had about the job or our company

This was a great way just to get to know the people who applied and get a gut sense for who stood out the most. We really enjoyed meeting with every single person we talked to, but made the hard decision to filter down again to about 10 people for the next phase.

## Take-Home Project

The next step in the application process was a take-home project, where the applicant had to build out a design Steve had created using either Vue or React. We estimated it to be about a 4-8 hour project.

We provided a zip file containing all of the instructions, the design as a Figma file, and a walk-through video of a working implementation outlining any behavior that was hard to capture in Figma.

[**See the take-home project on GitHub &rarr;**](https://github.com/adamwathan/tailwind-take-home-project)

We tried to give very clear instructions, and made sure to point out where we wanted people to focus their time, and what areas we didn't want them to overthink or spend too much time on.

We gave each candidate about two weeks to complete the project, just to make sure they had the opportunity to fit it into their schedule without it being disruptive.

All of the submissions we got back were great, but again we forced ourselves to limit the candidates for the next phase, this time down to 6 people.

One thing we really loved about Robin's submission was that he spent a lot of time guiding us through his solution with comments in the code. For regular production code I would say it was definitely overkill, but as part of a job application I thought it was extremely helpful to get a behind-the-scenes look into how he actually _thinks_ about the code he is writing. He also spent a lot of time describing alternate solutions to certain problems and why he didn't go with those approaches, which was very beneficial as well.

## Pairing Session

The final step in the application process was a two-hour pair programming session with me.

When pairing as part of an interview process like this, there's a really high risk of the inherent power dynamic coloring how the whole thing goes. I _really_ wanted to avoid that as much as possible, so I did two things:

- I made sure whatever we were pairing on was something completely new, that I had no prior experience with
- I let the candidate suggest a few things for us to pair on, and picked something from their list

I absolutely didn't want to pair on something where I knew all the answers and I was just watching to see if the candidate could figure out something I already knew. That is absolutely not representative of real work and I don't think it would've been useful at all.

Instead, by choosing a problem that neither of us had significant experience with, we got to put the power dynamic aside (as much as possible at least), and just focus on learning something new together, and seeing how we helped each other get unstuck.

Some of the things I paired on included:

- Building a date picker from scratch
- Learning XState
- Building a modal dialog with the Vue 3 composition API

I really enjoyed this process and am very proud of how we put it together. It was definitely the most informative part of the interview process and really gave me a ton of confidence that we were offering the job to the right person.

For Robin's session, we decided to build an SVG charting library from scratch _(something neither of us had ever done before)_, in Svelte _(a framework neither of us had ever used before)_. This was Robin's idea, and that he had the courage to tackle two completely new problems at the same time _in an interview context_ really impressed me. We had a great time pairing together on this, and not once in the session did it ever feel like either of us was ahead of the other person or trying to catch them up on something. We had really great chemistry and it felt very energizing and productive, and reminded me of some of the best pairing sessions I've had in my career, which is pretty incredible given we'd never worked together before, and that he was being evaluated for a job.

## Making the offer

This whole process took about 1.5 months, and at the end we had a very hard time choosing between the top few candidates. Realistically we could've hired any of them and not regretted it, but my experience interviewing and pairing with Robin stood out just a bit more and I was really excited to be able to offer him the role. We know he's going to be an amazing fit for the team, and I can't wait to dig in to some hard problems with him in the coming months.


================================================
FILE: src/pages/headless-ui-unstyled-accessible-ui-components/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'
import headlessUiCard from './headless-ui-card.svg'

export const meta = {
  title: `Headless UI: Unstyled, Accessible UI Components`,
  description: `Headless UI is a set of completely unstyled, fully accessible UI components for React, Vue, and Alpine.js that make it easy to build fully accessible custom UI components, without sacrificing the ability to style them from scratch with simple utility classes.`,
  date: '2020-10-06T18:30:00.000Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2508',
}

One of the biggest pain points when building modern web applications is building custom components like select menus, dropdowns, toggles, modals, tabs, radio groups — components that are pretty similar from project to project, but never quite the _same_.

You could use an off-the-shelf package, but they usually come tightly coupled with their own provided styles. It ends up being very hard to get them to match the look and feel of your own project, and almost always involves writing a bunch of CSS overrides, which feels like a big step backwards when working Tailwind CSS.

The other option is building your own components from scratch. At first it seems easy, but then you remember you need to add support for keyboard navigation, managing ARIA attributes, focus trapping, and all of a sudden you're spending 3-4 weeks trying to build a truly bullet-proof dropdown menu.

We think there's a better option, so we're building it.

<!--more-->

**[Headless UI](https://headlessui.dev) is a set of completely unstyled, fully accessible UI components for React and Vue** _(and soon Alpine.js)_ that make it easy to build these sorts of custom components without worrying about any of the complex implementation details yourself, and without sacrificing the ability to style them from scratch with simple utility classes.

<img src={headlessUiCard} alt="Headless UI Logo" />

Here's what it looks like to build a custom dropdown _(one of many components the library includes)_ using `@headlessui/react`, with complete keyboard navigation support and ARIA attribute management, styled with simple Tailwind CSS utilities:

```jsx
import { Menu } from '@headlessui/react'

function MyDropdown() {
  return (
    <Menu as="div" className="relative">
      <Menu.Button className="px-4 py-2 rounded bg-blue-600 text-white ...">Options</Menu.Button>
      <Menu.Items className="absolute mt-1 right-0">
        <Menu.Item>
          {({ active }) => (
            <a className={`${active && 'bg-blue-500 text-white'} ...`} href="/account-settings">
              Account settings
            </a>
          )}
        </Menu.Item>
        <Menu.Item>
          {({ active }) => (
            <a className={`${active && 'bg-blue-500 text-white'} ...`} href="/documentation">
              Documentation
            </a>
          )}
        </Menu.Item>
        <Menu.Item disabled>
          <span className="opacity-75 ...">Invite a friend (coming soon!)</span>
        </Menu.Item>
      </Menu.Items>
    </Menu>
  )
}
```

Here's what you're getting for free in that example, without having to write a single line of code related to it yourself:

- The dropdown panel opens on click, spacebar, enter, or when using the arrow keys
- The dropdown closes when you press escape, or click outside of it
- You can navigate the items using the up and down arrow keys
- You can jump the first item using the `Home` key, and the last item using the `End` key
- Disabled items are automatically skipped when navigating with the keyboard
- Hovering over an item with your mouse after navigating with the keyboard will switch to mouse position based focusing
- Items are announced properly to screen readers while navigating them with the keyboard
- The dropdown button is properly announced to screenreaders as controlling a menu
- ...and probably tons more that I'm forgetting.

All without writing the letters `aria` anywhere in your own code, and without writing a single event listener. And you still have complete control over the design!

There are [over 3000 lines of tests for this component](https://github.com/tailwindlabs/headlessui/blob/c7b91dc7315b1f49c1a469f70eb1f6eba6a2e31c/packages/%40headlessui-react/src/components/menu/menu.test.tsx). Pretty nice that you didn't have to do that yourself, right?

Here's a fully-styled live demo _(taken from [Tailwind UI](https://tailwindui.com))_ so you can see it in action:

<iframe
  src="https://codesandbox.io/embed/headlessuireact-menu-example-b6xje?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark"
  className="w-full border-0 rounded overflow-hidden"
  style={{ height: '480px' }}
  title="@headlessui/react Menu Example"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

Make sure to try it with the keyboard or a screen reader to really appreciate it!

We just tagged v0.2.0, which currently includes the following components:

- [Menu Button](https://codesandbox.io/s/headlessuivue-menu-example-70br3?file=/src/App.vue) (or dropdown)
- [Listbox](https://codesandbox.io/s/headlessuivue-listbox-example-mi67g?file=/src/App.vue) (or custom select)
- [Switch](https://codesandbox.io/s/headlessuivue-switch-example-8ycp6?file=/src/App.vue) (or toggle)
- ...with many more on the way.

To learn more and dive in, [**head over to the Headless UI website**](https://headlessui.dev) and read the documentation.

---

If you've followed my work online for the last few years, you might remember my fascination with [renderless UI components](https://adamwathan.me/renderless-components-in-vuejs/) — something I was really started getting into towards the [end of 2017](https://fullstackradio.com/79). I've wanted a library like this to exist for years, but until we started growing the team we just didn't have the resources to make it happen.

Earlier this year [we hired Robin Malfait](https://blog.tailwindcss.com/from-900-to-1-how-we-hired-robin-malfait), and he's been working on Headless UI full-time ever since.

The biggest motivation for this project is that we'd really like to add production-ready JS examples to [Tailwind UI](https://tailwindui.com), which is currently an HTML-only, bring-your-own-JavaScript sort of project. This is great for lots of our customers who want full control over how everything works, but for many others it's a point of friction.

We didn't want to add 200 lines of gnarly JS to every component example, so we started working on Headless UI as a way to _extract_ all of that noise, without giving up any flexibility in the actual UI design.

## Why reinvent the wheel?

We're not the first people to try and tackle this problem. [Downshift](https://github.com/downshift-js/downshift) was the first library I saw that got me excited about this idea back in 2017, [Reach UI](https://reach.tech/) and [Reakit](https://reakit.io/) started development in 2018, and [React Aria](https://react-spectrum.adobe.com/react-aria/getting-started.html) was released most recently, just earlier this year.

We decided to try our own take on the problem for a few reasons:

- Existing solutions are focused almost entirely on React, and we'd like to bring these ideas to other ecosystems like Vue, Alpine, and hopefully more in the future.
- These libraries are going to be foundational for adding JS support to Tailwind UI, and since that's what keeps the business running it felt important to have complete decision-making power over how the libraries worked and what they supported.
- We have our own ideas on what the APIs should look like for these components, and want to be able to explore those ideas freely.
- We want to make sure it is always super easy to style these components with Tailwind, rather than having to write custom CSS.

We think what we've come up with so far hits a great balance between flexibility and developer experience, and we're grateful there are other people working on similar problems that we can learn from and share our ideas with.

## What's next

We've got quite a few more components to develop for Headless UI, including:

- Modal
- Radio group
- Tabs
- Accordion
- Combobox
- Datepicker

...and likely many more. We're also about to start on Alpine.js support, and we're hoping to be able to tag a v1.0 for React, Vue, and Alpine near the end of the year.

After that we'll start exploring other frameworks, with the hope that we can eventually offer the same tools for ecosystems like Svelte, Angular, and Ember, either first-class or with community partners.

If you'd like to keep up with what we're doing, be sure to [follow the project on GitHub](https://github.com/tailwindlabs/headlessui).


================================================
FILE: src/pages/headless-ui-v1/.prettierrc
================================================
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": false
}


================================================
FILE: src/pages/headless-ui-v1/index.mdx
================================================
import { adamwathan } from '@/authors'
import card from './card.jpg'

export const meta = {
  title: 'Headless UI v1.0',
  description: `Last fall we announced Headless UI, a library of completely unstyled, fully accessible UI components, designed to pair perfectly with Tailwind CSS. Today we’re super excited to release Headless UI v1.0, which more than doubles the amount of included components for both React and Vue.`,
  date: '2021-04-14T16:00:00.000Z',
  authors: [adamwathan],
  image: card,
  footer: `
    <p>
      Want to try it out?
      <a href="https://headlessui.dev" class="font-medium text-teal-600 hover:text-teal-700">
        Visit the Headless UI website →
      </a>
    </p>
  `,
}

Last fall we announced [Headless UI](https://blog.tailwindcss.com/headless-ui-unstyled-accessible-ui-components), a library of completely unstyled, fully accessible UI components, designed to pair perfectly with Tailwind CSS.

Today we’re super excited to release [Headless UI v1.0](https://headlessui.dev), which more than doubles the amount of included components for both React and Vue.

<!--more-->

<a href="https://headlessui.dev">
  <img src={card} alt="Headless UI" />
</a>

## What’s new

We’ve added four new components to the React library, and five new components for Vue.

### Dialog (modal)

Headless UI now includes a robust dialog implementation you can use to build traditional modal dialogs, mobile slide-out menus, or any other take-over-style UI that needs to capture the focus of the entire page.

```jsx
import { useState } from 'react'
import { Dialog } from '@headlessui/react'

function MyDialog() {
  let [isOpen, setIsOpen] = useState(true)

  return (
    <Dialog open={isOpen} onClose={setIsOpen}>
      <Dialog.Overlay />

      <Dialog.Title>Deactivate account</Dialog.Title>
      <Dialog.Description>
        This will permanently deactivate your account
      </Dialog.Description>

      <p>
        Are you sure you want to deactivate your account? All of your data will
        be permanently removed. This action cannot be undone.
      </p>

      <button onClick={() => setIsOpen(false)}>Deactivate</button>
      <button onClick={() => setIsOpen(false)}>Cancel</button>
    </Dialog>
  )
}
```

### Disclosure

We’ve added a new `Disclosure` component that makes it easy to show/hide inline content accessibly. This is useful for things like collapsible FAQ questions, "show more" interfaces, or even hamburger menus that open up and push the rest of the page content away.

```html
<template>
  <Disclosure>
    <DisclosureButton> Is team pricing available? </DisclosureButton>
    <DisclosurePanel>
      Yes! You can purchase a license that you can share with your entire team.
    </DisclosurePanel>
  </Disclosure>
</template>

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from '@headlessui/vue'

  export default {
    components: { Disclosure, DisclosureButton, DisclosurePanel },
  }
</script>
```

### Radio Group

There’s now a `RadioGroup` component that you can use to build totally custom radio button UIs, like when you want to use fancy cards or something instead of a simple little radio circle.

```jsx
import { useState } from 'react'
import { RadioGroup } from '@headlessui/react'

function MyRadioGroup() {
  let [plan, setPlan] = useState('startup')

  return (
    <RadioGroup value={plan} onChange={setPlan}>
      <RadioGroup.Label>Plan</RadioGroup.Label>
      <RadioGroup.Option value="startup">
        {({ checked }) => (
          <span className={checked ? 'bg-blue-200' : ''}>Startup</span>
        )}
      </RadioGroup.Option>
      <RadioGroup.Option value="business">
        {({ checked }) => (
          <span className={checked ? 'bg-blue-200' : ''}>Business</span>
        )}
      </RadioGroup.Option>
      <RadioGroup.Option value="enterprise">
        {({ checked }) => (
          <span className={checked ? 'bg-blue-200' : ''}>Enterprise</span>
        )}
      </RadioGroup.Option>
    </RadioGroup>
  )
}
```

### Popover

The new `Popover` component lets you build custom dropdown UIs that don’t have any content restrictions like a regular `Menu` component would. Great for fly-out menus on marketing sites, dropdowns that have form fields in them, and tons more.

```html
<template>
  <Popover class="relative">
    <PopoverButton>Solutions</PopoverButton>

    <PopoverPanel class="absolute z-10">
      <div>
        <a href="/analytics">Analytics</a>
        <a href="/engagement">Engagement</a>
        <a href="/security">Security</a>
        <a href="/integrations">Integrations</a>
      </div>

      <img src="/solutions.jpg" alt="" />
    </PopoverPanel>
  </Popover>
</template>

<script>
  import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'

  export default {
    components: { Popover, PopoverButton, PopoverPanel },
  }
</script>
```

### TransitionRoot and TransitionChild (for Vue)

Headless UI already had a `Transition` component for React, but we’ve always recommended the native `<transition>` that already ships with Vue for Vue users. There are some limitations to the native transition though, and things can get complicated when trying to co-ordinate nested transitions that are supposed to run in parallel.

Headless UI v1.0 brings our React `Transition` component to Vue as well, which makes it a lot easier to transition things like modal dialogs.

```html
<template>
  <!— This `show` prop controls all nested `Transition.Child` components. —>
  <TransitionRoot :show="isOpen">
    <!— Background overlay —>
    <TransitionChild
      enter="transition-opacity"
      ease-linear
      duration-300"
      enter-from="opacity-0"
      enter-to="opacity-100"
      leave="transition-opacity"
      ease-linear
      duration-300"
      leave-from="opacity-100"
      leave-to="opacity-0"
    >
      <!— … —>
    </TransitionChild>

    <!— Sliding sidebar —>
    <TransitionChild
      enter="transition"
      ease-in-out
      duration-300
      transform"
      enter-from="-translate-x-full"
      enter-to="translate-x-0"
      leave="transition"
      ease-in-out
      duration-300
      transform"
      leave-from="translate-x-0"
      leave-to="-translate-x-full"
    >
      <!— … —>
    </TransitionChild>
  </TransitionRoot>
</template>

<script>
  import { ref } from "vue";
  import { Transition, TransitionChild } from "@headlessui/vue";

  export default {
    components: { TransitionRoot: Transition, TransitionChild },

    setup() {
      const isShowing = ref(true);

      return {
        isShowing,
      };
    },
  };
</script>
```

## Try it out

Head over to our [brand new documentation website](https://headlessui.dev) to pull Headless UI into your projects and play with it! It’s MIT licensed and open-source, so if you’d like to poke around the code or you need to report an issue, [visit the GitHub repository](https://github.com/tailwindlabs/headlessui).


================================================
FILE: src/pages/headless-ui-v1-4/.prettierrc
================================================
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": false
}


================================================
FILE: src/pages/headless-ui-v1-4/index.mdx
================================================
import { adamwathan, robinmalfait } from '@/authors'
import card from './card.jpg'
import banner from './banner.jpg'
import ecommerce from './ecommerce-screenie.jpg'
import { Tab } from '@headlessui/react'
import ReactExample1 from './snippets/react-1.mdx'
import ReactExample2 from './snippets/react-2.mdx'
import ReactExample3 from './snippets/react-3.mdx'
import ReactExample4 from './snippets/react-4.mdx'
import VueExample1 from './snippets/vue-1.mdx'
import VueExample2 from './snippets/vue-2.mdx'
import VueExample3 from './snippets/vue-3.mdx'
import VueExample4 from './snippets/vue-4.mdx'

export const meta = {
  title: 'Headless UI v1.4: The One With Tabs',
  description: `We just released Headless UI v1.4, which includes a brand new \`Tab\` component, and new APIs for manually closing \`Popover\` and \`Disclosure\` components more easily.`,
  date: '2021-07-29T12:00:00.000Z',
  authors: [adamwathan, robinmalfait],
  image: card,
  footer: `
    <p>
      Ready to try it out?
      <a href="https://headlessui.dev" class="font-medium text-teal-600 hover:text-teal-700">
        Visit the Headless UI website →
      </a>
    </p>
  `,
}

We just released Headless UI v1.4, which includes a brand new `Tab` component, and new APIs for manually closing `Popover` and `Disclosure` components more easily.

<!--more-->

<a href="https://headlessui.dev">
  <img src={banner} alt="Headless UI v1.4" />
</a>

## Tabs

Earlier this year we started working on [Tailwind UI Ecommerce](https://tailwindui.com/ecommerce), and we realized pretty quickly we were going to need to support tabs in Headless UI to be able to build the new interfaces we were designing.

<a href="https://tailwindui.com/ecommerce">
  <img
    src={ecommerce}
    alt="Product details interface design from Tailwind UI Ecommerce."
  />
</a>

Here's what we ended up with:

<Tab.Group as="div" className="my-6 bg-gray-800 rounded-md">
  <Tab.List className="relative space-x-2 px-4 pt-3">
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      React
    </Tab>
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      Vue
    </Tab>
  </Tab.List>
  <Tab.Panels>
    <Tab.Panel className="focus:outline-none">
      <ReactExample1 />
    </Tab.Panel>
    <Tab.Panel className="focus:outline-none">
      <VueExample1 />
    </Tab.Panel>
  </Tab.Panels>
</Tab.Group>

And yep, those are tabs!

Like all Headless UI components, this totally abstracts away stuff like keyboard navigation for you so you can create custom tabs in a completely declarative way, without having to think about any of the tricky accessibility details.

[Check out the documentation](https://headlessui.dev/react/tabs) to learn more.

### Closing Disclosures and Popovers

Up until now, there was no way to close a `Disclosure` without clicking the actual button used to open it. For typical disclosure use cases this isn't a big deal, but it often makes sense to use disclosures for things like mobile navigation, where you want to close it when someone clicks a link _inside_ of it.

Now you can use `Disclosure.Button` or (`DisclosureButton` in Vue) within your disclosure panel to close the panel, making it easy to wrap up things like links or other buttons so the panel doesn't stay open:

<Tab.Group as="div" className="my-6 bg-gray-800 rounded-md">
  <Tab.List className="relative space-x-2 px-4 pt-3">
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      React
    </Tab>
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      Vue
    </Tab>
  </Tab.List>
  <Tab.Panels>
    <Tab.Panel className="focus:outline-none">
      <ReactExample2 />
    </Tab.Panel>
    <Tab.Panel className="focus:outline-none">
      <VueExample2 />
    </Tab.Panel>
  </Tab.Panels>
</Tab.Group>

The same thing works with `Popover` components, too:

<Tab.Group as="div" className="my-6 bg-gray-800 rounded-md">
  <Tab.List className="relative space-x-2 px-4 pt-3">
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      React
    </Tab>
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      Vue
    </Tab>
  </Tab.List>
  <Tab.Panels>
    <Tab.Panel className="focus:outline-none">
      <ReactExample3 />
    </Tab.Panel>
    <Tab.Panel className="focus:outline-none">
      <VueExample3 />
    </Tab.Panel>
  </Tab.Panels>
</Tab.Group>

If you need finer control, we also pass a `close` function via the render prop/scoped slot, so you can imperatively close the panel when you need to:

<Tab.Group as="div" className="my-6 bg-gray-800 rounded-md">
  <Tab.List className="relative space-x-2 px-4 pt-3">
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      React
    </Tab>
    <Tab
      className={({ selected }) =>
        `py-2 px-4 inline-block rounded-md ${
          selected ? 'bg-gray-700' : 'bg-transparent hover:bg-gray-700'
        } text-sm text-white font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-600`
      }
    >
      Vue
    </Tab>
  </Tab.List>
  <Tab.Panels>
    <Tab.Panel className="focus:outline-none">
      <ReactExample4 />
    </Tab.Panel>
    <Tab.Panel className="focus:outline-none">
      <VueExample4 />
    </Tab.Panel>
  </Tab.Panels>
</Tab.Group>

For more details, check out the updated [Popover](https://headlessui.dev/react/popover#closing-popovers-manually) and [Disclosure](https://headlessui.dev/react/disclosure#closing-disclosures-manually) documentation.

## Try it out

Headless UI v1.4 is a minor update so there are no breaking changes. To upgrade, just install the latest version via npm:

```shell
# For React
npm install @headlessui/react

# For Vue
npm install @headlessui/vue
```

Check out [the official website](https://headlessui.dev) for the latest documentation, and check out [Tailwind UI](https://tailwindui.com) if you want to play tons of styled examples.


================================================
FILE: src/pages/headless-ui-v1-4/snippets/react-1.mdx
================================================
```jsx
import { Tab } from '@headlessui/react'

function MyTabs() {
  return (
    <Tab.Group>
      <Tab.List>
        <Tab>Tab 1</Tab>
        <Tab>Tab 2</Tab>
        <Tab>Tab 3</Tab>
      </Tab.List>
      <Tab.Panels>
        <Tab.Panel>Content 1</Tab.Panel>
        <Tab.Panel>Content 2</Tab.Panel>
        <Tab.Panel>Content 3</Tab.Panel>
      </Tab.Panels>
    </Tab.Group>
  )
}
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/react-2.mdx
================================================
```jsx
import { Disclosure } from '@headlessui/react'
import MyLink from './MyLink'

function MyDisclosure() {
  return (
    <Disclosure>
      <Disclosure.Button>Open mobile menu</Disclosure.Button>
      <Disclosure.Panel>
        <Disclosure.Button as={MyLink} href="/home">
          Home
        </Disclosure.Button>
        {/* ... */}
      </Disclosure.Panel>
    </Disclosure>
  )
}
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/react-3.mdx
================================================
```jsx
import { Popover } from '@headlessui/react'
import MyLink from './MyLink'

function MyPopover() {
  return (
    <Popover>
      <Popover.Button>Solutions</Popover.Button>
      <Popover.Panel>
        <Popover.Button as={MyLink} href="/insights">
          Insights
        </Popover.Button>
        {/* ... */}
      </Popover.Panel>
    </Popover>
  )
}
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/react-4.mdx
================================================
```jsx
import { Popover } from '@headlessui/react'

function MyPopover() {
  return (
    <Popover>
      <Popover.Button>Terms</Popover.Button>
      <Popover.Panel>
        {({ close }) => (
          <button
            onClick={async () => {
              await fetch('/accept-terms', { method: 'POST' })
              close()
            }}
          >
            Read and accept
          </button>
        )}
      </Popover.Panel>
    </Popover>
  )
}
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/vue-1.mdx
================================================
```html
<template>
  <TabGroup>
    <TabList>
      <Tab>Tab 1</Tab>
      <Tab>Tab 2</Tab>
      <Tab>Tab 3</Tab>
    </TabList>
    <TabPanels>
      <TabPanel>Content 1</TabPanel>
      <TabPanel>Content 2</TabPanel>
      <TabPanel>Content 3</TabPanel>
    </TabPanels>
  </TabGroup>
</template>

<script>
  import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'

  export default {
    components: {
      TabGroup,
      TabList,
      Tab,
      TabPanels,
      TabPanel,
    },
  }
</script>
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/vue-2.mdx
================================================
```html
<template>
  <Disclosure>
    <DisclosureButton>Open mobile menu</DisclosureButton>
    <DisclosurePanel>
      <DisclosureButton :as="MyLink" href="/home">Home</DisclosureButton>
      <!-- ... -->
    </DisclosurePanel>
  </Disclosure>
</template>

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from '@headlessui/vue'
  import MyLink from './MyLink'

  export default {
    components: { Disclosure, DisclosureButton, DisclosurePanel, MyLink },
  }
</script>
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/vue-3.mdx
================================================
```html
<template>
  <Popover>
    <PopoverButton>Solutions</PopoverButton>

    <PopoverPanel>
      <PopoverButton :as="MyLink" href="/insights">Insights</PopoverButton>
      <!-- ... -->
    </PopoverPanel>
  </Popover>
</template>

<script>
  import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
  import MyLink from './MyLink'

  export default {
    components: { Popover, PopoverButton, PopoverPanel, MyLink },
  }
</script>
```


================================================
FILE: src/pages/headless-ui-v1-4/snippets/vue-4.mdx
================================================
```html
<template>
  <Popover>
    <PopoverButton>Solutions</PopoverButton>

    <PopoverPanel v-slot="{ close }">
      <button @click="accept(close)">Read and accept</button>
    </PopoverPanel>
  </Popover>
</template>

<script>
  import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'

  export default {
    components: { Popover, PopoverButton, PopoverPanel },
    setup() {
      return {
        accept: async (close) => {
          await fetch('/accept-terms', { method: 'POST' })
          close()
        },
      }
    },
  }
</script>
```


================================================
FILE: src/pages/heroicons-v1/.prettierrc
================================================
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": false
}


================================================
FILE: src/pages/heroicons-v1/index.mdx
================================================
import { adamwathan } from '@/authors'
import card from './card.jpg'

export const meta = {
  title: 'Heroicons v1.0',
  description: `Today we're releasing Heroicons v1.0, which includes over 450+ free icons in two styles, official React and Vue libraries, and Figma assets.`,
  date: '2021-03-29T19:00:00.000Z',
  authors: [adamwathan],
  image: card,
  footer: `
    <p>
      Want to start playing with it?
      <a href="https://heroicons.com" class="font-medium text-teal-600 hover:text-teal-700">
        Visit the Heroicons website →
      </a>
    </p>
  `,
}

Just over a year ago we released the very first version of [Heroicons](https://heroicons.com), which is a set of beautiful UI icons we designed alongside Tailwind UI. Since then we've added tons of new icons, and designed and launched a dedicated web experience.

Today we're excited to finally release Heroicons v1.0, which includes over 450+ free icons in two styles, official React and Vue libraries, and Figma assets.

<!--more-->

<a href="https://heroicons.com">
  <img src={card} alt="Heroicons" />
</a>

## React + Vue Libraries

In addition to grabbing the icons you need directly from the website, you can now install our official React and Vue libraries for quick and easy access to each icon as a dedicated component.

Here's what it looks like with React for example:

```js
import { BeakerIcon } from '@heroicons/react/solid'

function MyComponent() {
  return (
    <div>
      <BeakerIcon className="h-5 w-5 text-blue-500" />
      <p>...</p>
    </div>
  )
}
```

[Check out the documentation](https://github.com/tailwindlabs/heroicons) on GitHub to learn more.

## Figma Assets

We've also published an official [Heroicons Figma file](https://www.figma.com/community/file/958423903283802665/heroicons) on our new Figma Community page!

It includes all the icons from Heroicons as individual Figma components so you can easily use them in your projects without having to manually import each SVG.


================================================
FILE: src/pages/index.js
================================================
import tinytime from 'tinytime'
import Link from 'next/link'
import Head from 'next/head'
import getAllPostPreviews from '@/getAllPostPreviews'
import twitterCard from '@/img/twitter-card.jpg'
import Header from '@/components/Header'
import SectionContainer from '@/components/SectionContainer'

const posts = getAllPostPreviews()

const postDateTemplate = tinytime('{MMMM} {DD}, {YYYY}')

export default function Home() {
  return (
    <>
      <SectionContainer>
        <Header />
      </SectionContainer>
      <SectionContainer>
        <main>
          <div className="divide-y divide-gray-200">
            <Head>
              <meta name="twitter:card" content="summary_large_image" />
              <meta name="twitter:site" content="@tailwindcss" />
              <meta name="twitter:creator" content="@tailwindcss" />
              <meta name="twitter:title" content="Blog – Tailwind CSS" />
              <meta name="twitter:description" content="News content from the Tailwind CSS team." />
              <meta name="twitter:image" content={`https://blog.tailwindcss.com${twitterCard}`} />
              <meta property="og:url" content="https://blog.tailwindcss.com" />
              <meta property="og:type" content="article" />
              <meta property="og:title" content="Blog – Tailwind CSS" />
              <meta property="og:description" content="News content from the Tailwind CSS team." />
              <meta property="og:image" content={`https://blog.tailwindcss.com${twitterCard}`} />
              <title>Blog – Tailwind CSS</title>
              <meta name="description" content="News content from the Tailwind CSS team." />
            </Head>
            <div className="pt-6 pb-8 space-y-2 md:space-y-5">
              <h1 className="text-3xl font-extrabold text-gray-900 tracking-tight sm:text-4xl md:text-[4rem] md:leading-[3.5rem]">
                Latest
              </h1>
              <p className="text-lg text-gray-500">
                All the latest Tailwind CSS news, straight from the team.
              </p>
            </div>
            <ul className="divide-y divide-gray-200">
              {posts.map(({ link, module: { default: Component, meta } }) => {
                return (
                  <li key={link} className="py-12">
                    <article className="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline">
                      <dl>
                        <dt className="sr-only">Published on</dt>
                        <dd className="text-base font-medium text-gray-500">
                          <time dateTime={meta.date}>
                            {postDateTemplate.render(new Date(meta.date))}
                          </time>
                        </dd>
                      </dl>
                      <div className="space-y-5 xl:col-span-3">
                        <div className="space-y-6">
                          <h2 className="text-2xl font-bold tracking-tight">
                            <Link href={link}>
                              <a className="text-gray-900">{meta.title}</a>
                            </Link>
                          </h2>
                          <div className="prose max-w-none text-gray-500">
                            <Component />
                          </div>
                        </div>
                        <div className="text-base font-medium">
                          <Link href={link}>
                            <a
                              className="text-teal-600 hover:text-teal-700"
                              aria-label={`Read "${meta.title}"`}
                            >
                              Read more &rarr;
                            </a>
                          </Link>
                        </div>
                      </div>
                    </article>
                  </li>
                )
              })}
            </ul>
          </div>
        </main>
      </SectionContainer>
    </>
  )
}


================================================
FILE: src/pages/introducing-heroicons/index.mdx
================================================
import { steveschoger } from '@/authors'
import card from './card.jpg'
import banner from './banner.svg'
import iconStyles from './icon-styles.svg'
import heroPatternsPreview from './heropatterns-preview.jpg'

export const meta = {
  title: 'Introducing Heroicons.com',
  description: `Today we're launching the official Heroicons web experience, which makes it easier than every to search for icons and quickly copy them to your clipboard as Tailwind-ready HTML or JSX.`,
  date: '2020-08-25T13:00:00.000Z',
  authors: [steveschoger],
  image: card,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2238',
}

A few months back we quietly released [Heroicons](https://www.heroicons.com/), a set of free SVG icons we initially designed to support the components in Tailwind UI. Today we’re launching the official [Heroicons web experience](https://www.heroicons.com/), which makes it easier than ever to search for icons and quickly copy them to your clipboard as Tailwind-ready HTML or JSX.

<!--more-->

<a href="https://heroicons.com">
  <img src={banner} alt="heroicons.com" />
</a>

There are currently over 220 icons available in both medium and small sizes, with size designed to serve a different use-case:

- Medium icons are designed to be rendered at 24x24, and work well for things like primary navigation and marketing sections.
- Small icons are designed to be rendered at 20x20, and work well for buttons, form elements and to support text.

All of the icons are Tailwind-ready, and are easy to style with Tailwind’s built-in size and color utilities.

```html
<svg
  class="h-6 w-6 text-indigo-500"
  xmlns="http://www.w3.org/2000/svg"
  fill="none"
  viewBox="0 0 24 24"
  stroke="currentColor"
>
  <path
    stroke-linecap="round"
    stroke-linejoin="round"
    stroke-width="2"
    d="M17 8l4 4m0 0l-4 4m4-4H3"
  />
</svg>
```

For best results, use h-6 w-6 for medium icons, and h-5 w-5 for small icons.

## Just the beginning

We’ve got lots of ideas for both new icons, as well as new icon styles _(duotone anyone?)_ that we’re excited to design and release in the coming months.

<img src={iconStyles} alt="" />

Designing this site also got me itching to refresh the Hero Patterns site, so you’ll probably see something like this show up at [heropatterns.com](http://www.heropatterns.com/) pretty soon:

<img src={heroPatternsPreview} alt="" />

We’ve got a bunch of [other “Hero” domains](https://twitter.com/steveschoger/status/1266042614710767616?s=20) waiting to be put to use too, and I’m pumped to reveal what we’re working on for those soon.

## Got a suggestion?

If you have any ideas for new icons we’d love to hear them! Head over to the [Heroicons GitHub repository](https://github.com/tailwindlabs/heroicons) and open an issue to make a suggestion.


================================================
FILE: src/pages/introducing-linting-for-tailwindcss-intellisense/index.mdx
================================================
import { bradlc } from '@/authors'
import imgCss from './css.png'
import imgCss2x from './css@2x.png'
import imgHtml from './html.png'
import imgHtml2x from './html@2x.png'
import applyQuickFixVideo from './apply-quick-fix.mp4'
import image from './card.jpg'

export const meta = {
  title: 'Introducing linting for Tailwind CSS IntelliSense',
  description:
    'Today we’re releasing a new version of the Tailwind CSS IntelliSense extension for Visual Studio Code that adds Tailwind-specific linting to both your CSS and your markup.',
  date: '2020-06-23T18:52:03Z',
  authors: [bradlc],
  image,
  discussion: 'https://github.com/tailwindcss/tailwindcss/discussions/1956',
}

Today we’re releasing a new version of the [Tailwind CSS IntelliSense extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) that adds Tailwind-specific linting to both your CSS and your markup.

<!--more-->

## Detecting errors in your CSS

Tailwind already detects CSS errors, for example when you mistype a screen name in the `@screen` directive. The linting feature for Tailwind CSS IntelliSense surfaces these errors and displays them in context, directly inside your editor. The linter will validate your `@tailwind`, `@screen`, `@variants` and `@apply` directives, as well as any `theme` function calls:

<img
  width="1524"
  height="857"
  alt="Screen capture showing CSS linting in action"
  src={imgCss}
  srcSet={`${imgCss} 762w, ${imgCss2x} 1524w`}
  sizes="(min-width: 1280px) 51rem, (min-width: 768px) 45rem, (min-width: 640px) calc(100vw - 3rem), calc(100vw - 2rem)"
/>

## Catching conflicts in your HTML

There is one more lint rule which analyses class lists in your template files and highlights any instances where utilities seem to be in conflict. For example you probably didn’t intend to have `mt-4` and `mt-6` in the same class list!

<img
  width="1524"
  height="857"
  alt="Screen capture showing markup linting in action"
  src={imgHtml}
  srcSet={`${imgHtml} 762w, ${imgHtml2x} 1524w`}
  sizes="(min-width: 1280px) 51rem, (min-width: 768px) 45rem, (min-width: 640px) calc(100vw - 3rem), calc(100vw - 2rem)"
/>

## Quick fixes included

To make it as easy as possible to fix any issues, all of the lint rules have their own "quick fixes" which can be triggered directly within Visual Studio Code. If you accidentally typed `@screen small` instead of `@screen sm`, the editor can automatically replace `small` with `sm` for you!

As well as simple text replacements there’s also some more interesting quick fixes for the more complex lint rules. Take a look at how the extension can automatically refactor an invalid `@apply` directive:

<video src={applyQuickFixVideo} width="1600" height="900" controls />

## Configuration

We think you’ll love the new lint feature, but if you don’t, or you just want to tweak some behavior, we’ve got you covered. You can decide how each rule violation is treated: is it an `error`, or just a `warning`, or do you want to `ignore` the rule altogether? If you really want to you can disable linting entirely using the new `tailwindCSS.validate` setting.

Check out the [extension readme](https://github.com/tailwindcss/intellisense#tailwindcssvalidate) for more details about configuring the lint rules to suit your workflow.

## Conclusion

Linting is available now in `v0.4.0` of Tailwind CSS IntelliSense! If you already have the extension you may need to reload Visual Studio Code to get the update, and if you don’t you can install it via the [extension marketplace](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss).

This is the very first iteration of this feature, and we’d love to hear your feedback! Do you have an idea for a new lint rule? Let us know!


================================================
FILE: src/pages/introducing-tailwind-play/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: `Introducing Tailwind Play`,
  description: `Tailwind Play is an advanced online playground for Tailwind CSS that lets you use all of Tailwind's build-time features directly in the browser.`,
  date: '2020-10-07T13:00:00.000Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2511',
}

To get the most out of Tailwind, you need a build step. It's the only way to be able to customize your [`tailwind.config.js`](https://tailwindcss.com/docs/configuration) file, extract components with [`@apply`](https://tailwindcss.com/docs/functions-and-directives#apply), or include [plugins](https://tailwindcss.com/docs/plugins).

This isn't a problem if you've already bought in to the framework, but if you're just trying to kick the tires for the first time it's a lot of friction. You either have to set up a local development environment with PostCSS support, or stick to the static CDN build, which means you lose out on lots of cool features.

So today we're excited to release the first version of [**Tailwind Play**](https://play.tailwindcss.com), an advanced online playground for Tailwind CSS that lets you use all of Tailwind's build-time features directly in the browser.

<!--more-->

<div className="my-8 aspect-w-16 aspect-h-9">
  <iframe
    src="https://www.youtube.com/embed/eCWhTZ34Hck"
    frameBorder="0"
    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
    allowFullScreen
  ></iframe>
</div>

It includes support for all of Tailwind's coolest features, plus tons of stuff that's even better in Tailwind Play than it is in your editor, like:

- Customizing your Tailwind theme
- Enabling special variants, like `group-hover` or `focus-within`
- Using custom directives in your CSS like `@apply`, `@variants`, and `@responsive`
- Adding plugins like `@tailwindcss/typography`
- Intelligent code completion and linting
- Responsive design mode
- One-click sharing

The code completion even updates the rendered preview in _real-time_, which creates an incredible design workflow in the browser — just navigate through different padding utilities with the arrow keys for example to find the perfect value without ever saving the file or even hitting enter!

Our responsive design mode that lets you fine-tune the viewport while you're working on your design, just like you can in Chrome DevTools. You can even drag the viewport beyond the available space, and the preview area will automatically zoom out, letting you design for larger screens even when you have limited space.

One-click sharing really is just that — you don't even need to create an account. Click "Share" and you've immediately got a link to a snapshot of what you're working on that you can share online.

**Check it out at [play.tailwindcss.com](https://play.tailwindcss.com)** and let us know what you think!


================================================
FILE: src/pages/just-in-time-the-next-generation-of-tailwind-css/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: `Just-In-Time: The Next Generation of Tailwind CSS`,
  description: `One of the hardest constraints we've had to deal with as we've improved Tailwind CSS over the years is the generated file size in development. Today I'm super excited to share a new project that makes this constraint a thing of the past: a just-in-time compiler for Tailwind CSS.`,
  date: '2021-03-15T16:30:00.000Z',
  authors: [adamwathan],
  image,
  footer: `
    <p>
      Ready to try it out?
      <a href="https://github.com/tailwindlabs/tailwindcss-jit" class="font-medium text-teal-600 hover:text-teal-700">
        Get started →
      </a>
    </p>
  `,
}

_**Update**: As of Tailwind CSS v2.1, the new Just-in-Time engine is included right in Tailwind CSS itself, so you don't need the `@tailwindcss/jit` package anymore. [Learn more in the documentation](https://tailwindcss.com/docs/just-in-time-mode)._

One of the hardest constraints we've had to deal with as we've improved Tailwind CSS over the years is the generated file size in development. With enough customizations to your config file, the generated CSS can reach 10mb or more, and there's only so much CSS that build tools and even the browser itself will comfortably tolerate.

For that reason, you've always had to be careful about expensive changes to your config file like adding too many extra breakpoints or enabling extra variants like `disabled` or `focus-visible`.

Today I'm super excited to share a new project we've been working on that makes these considerations a thing of the past: [**a just-in-time compiler for Tailwind CSS**](https://github.com/tailwindlabs/tailwindcss-jit).

<!--more-->

<div className="aspect-w-16 aspect-h-9 my-12">
  <iframe
    src="https://www.youtube.com/embed/3O_3X7InOw8"
    frameBorder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
    allowFullScreen
  ></iframe>
</div>

[**@tailwindcss/jit**](https://github.com/tailwindlabs/tailwindcss-jit) is a new experimental library that compiles all of your CSS _on-demand_ as you author your template files, instead of generating your entire stylesheet up front.

This comes with a lot of advantages:

- **Lightning fast build times**. Tailwind can take 3–8s to initially compile using our CLI, and upwards of 30–45s in webpack projects because webpack struggles with large CSS files. This library can compile even the biggest projects in about 800ms _(with incremental rebuilds as fast as 3ms)_, no matter what build tool you're using.
- **Every variant is enabled out of the box**. Variants like `focus-visible`, `active`, `disabled`, and others are not normally enabled by default due to file-size considerations. Since this library generates styles on demand, you can use any variant you want, whenever you want. You can even stack them like `sm:hover:active:disabled:opacity-75`. Never configure your variants again.
- **Generate arbitrary styles without writing custom CSS.** Ever needed some ultra-specific value that wasn't part of your design system, like `top: -113px` for a quirky background image? Since styles are generated on demand, you can just generate a utility for this as needed using square bracket notation like `top-[-113px]`. Works with variants too, like `md:top-[-113px]`.
- **Your CSS is identical in development and production**. Since styles are generated as they are needed, you don't need to purge unused styles for production, which means you see the exact same CSS in all environments. Never worry about accidentally purging an important style in production again.
- **Better browser performance in development**. Since development builds are as small as production builds, the browser doesn't have to parse and manage multiple megabytes of pre-generated CSS. In projects with heavily extended configurations this makes dev tools a lot more responsive.

Try it today by installing `@tailwindcss/jit` and swapping it into your PostCSS configuration:

```shell
npm install -D @tailwindcss/jit tailwindcss postcss autoprefixer
```

```js
// postcss.config.js
module.exports = {
  plugins: {
    '@tailwindcss/jit': {},
    autoprefixer: {},
  },
}
```

We're shipping it as a separate library for now, but once we've worked out all the kinks we're going to roll it right back into `tailwindcss` behind a configuration option, and we're aiming to make it the default in Tailwind CSS v3.0 later this year.

[Learn more about the project on GitHub](https://github.com/tailwindlabs/tailwindcss-jit), then install it, play with it, bend it, break it, and let us know what you think!


================================================
FILE: src/pages/multi-line-truncation-with-tailwindcss-line-clamp/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Multi-line truncation with @tailwindcss/line-clamp',
  description: `A few weeks back we released @tailwindcss/line-clamp, an official Tailwind CSS plugin for truncating text to a specific number of lines.`,
  date: '2021-01-24T20:00:00Z',
  authors: [adamwathan],
  image,
}

<!--excerpt-->

Imagine you're implementing a beautiful design you or someone on your team carefully crafted in Figma. You've nailed all the different layouts at each breakpoint, perfected the whitespace and typography, and the photography you're using is really bringing the design to life.

It looks totally amazing — until you connect it your actual production content and realize that your beautiful grid of blog cards falls apart because, of course, _real_ article excerpts aren't all magically exactly three lines long, and now each card is a different height.

Sound familiar? If so, the line-clamp plugin is here to save your bacon.

<!--/excerpt-->

<p className="lead">
  A few weeks back we released{' '}
  <a href="https://github.com/tailwindlabs/tailwindcss-line-clamp">
    <code>@tailwindcss/line-clamp</code>
  </a>
  , an official Tailwind CSS plugin for truncating text to a specific number of lines.
</p>

Imagine you're implementing a beautiful design you or someone on your team carefully crafted in Figma. You've nailed all the different layouts at each breakpoint, perfected the whitespace and typography, and the photography you're using is really bringing the design to life.

It looks totally amazing — until you connect it your actual production content and realize that your beautiful grid of blog cards falls apart because, of course, _real_ article excerpts aren't all magically exactly three lines long, and now each card is a different height.

Sound familiar? If so, the line-clamp plugin is here to save your bacon.

<div className="relative" style={{ paddingBottom: '56.25%' }}>
  <iframe
    className="absolute inset-0 h-full w-full"
    src="https://www.youtube.com/embed/klh-jMTm5PU"
    frameBorder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
    allowFullScreen
  ></iframe>
</div>

First, install the plugin and add it to your `tailwind.config.js` file:

```shell
npm install @tailwindcss/line-clamp
```

```js
// tailwind.config.js
module.exports = {
  // ...
  plugins: [
    // ...
    require('@tailwindcss/line-clamp'),
  ],
}
```

Then all you need to do is add a `line-clamp-{n}` utility to any block of text to automatically truncate to _n_ lines with a trailing ellipsis:

```html
<p class="line-clamp-3">
  Here's a block of text from a blog post that isn't conveniently three lines long like you designed
  for originally. It's probably like 6 lines on mobile or even on desktop depending on how you have
  things laid out. Truly a big pain in the derriere, and not the sort of thing you expected to be
  wasting your time trying to deal with at 4:45pm on a Friday am I right? You've got tickets to
  SmackDown and you heard there's gonna be a dark match with that local guy from two towns over that
  your cousin went to high school with before the show starts, and you're gonna miss it if you're
  not there early.
</p>
```

For more details, [check out the documentation](https://github.com/tailwindlabs/tailwindcss-line-clamp/) over on the GitHub repository.


================================================
FILE: src/pages/simon-vrachliotis-joins-tailwind-labs/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Simon Vrachliotis Joins Tailwind Labs',
  description: `Today we are super excited to share that Simon Vrachliotis has joined the development team at Tailwind Labs.`,
  date: '2020-07-19T19:00:00.000Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindcss/tailwindcss/discussions/2043',
}

Today we are super excited to share that [Simon Vrachliotis](https://twitter.com/simonswiss) has joined the development team at Tailwind Labs! (We just finalized that new business name by the way, pretty cool right?)

Simon has been a utility-first true believer since before Tailwind even existed, and authored an oft-referenced case study on his experience [rebuilding his company’s entire website with functional CSS in 10 days](https://hackernoon.com/full-re-write-with-tachyons-and-functional-css-a-case-study-part-1-635ccb5fb00b) way back in February 2017.

<!--more-->

He also created the [first-ever Tailwind CSS video course](https://egghead.io/courses/build-user-interfaces-by-composing-css-utility-classes-with-tailwind), published on egghead.io only 4 months after we released v0.1.0.

Steve and I met Simon for the first time when we visited Sydney for Laracon AU back in 2018, where Simon was giving a talk on, of course, utility-first CSS:

<figure>
  <div className="aspect-w-16 aspect-h-9">
    <iframe
      src="https://player.vimeo.com/video/294976504"
      frameBorder="0"
      allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
      allowFullScreen
    ></iframe>
  </div>
  <figcaption className="italic">
    We know this video isn't from Laracon, but this recording turned out better :)
  </figcaption>
</figure>

He knocked it out of the park, and it has been an absolute pleasure getting to know Simon better over the time since we first met.

Simon is a talented developer, an amazing teacher, and has such a contagious enthusiasm for the sort of work we do that we knew we had to have him on the team if we ever had the chance.

He’s joining in a product and community focused role, and will be doing lots of amazing work helping us build things like Tailwind UI, as well as creating educational resources to help even more people have success with Tailwind CSS.

We couldn’t be more excited to be welcoming him to the team!


================================================
FILE: src/pages/tailwind-ui-ecommerce/.prettierrc
================================================
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": false
}


================================================
FILE: src/pages/tailwind-ui-ecommerce/index.mdx
================================================
import { adamwathan } from '@/authors'
import card from './card.jpg'
import productPagePreview from './product-page-preview.jpg'

export const meta = {
  title: 'Introducing Tailwind UI Ecommerce',
  description: `Almost 6 months in the making, we finally released Tailwind UI Ecommerce — the first all-new component kit for Tailwind UI since the initial launch back in February 2020.`,
  date: '2021-08-11T19:30:00.000Z',
  authors: [adamwathan],
  image: card,
  footer: `
    <p>
      Want to check it out?
      <a href="https://tailwindui.com" class="font-medium text-teal-600 hover:text-teal-700">
        Visit the Tailwind UI website →
      </a>
    </p>
  `,
}

<!--excerpt-->

Almost 6 months in the making, we finally released [Tailwind UI Ecommerce](https://tailwindui.com/#product-ecommerce) — the first all-new component kit for Tailwind UI since the initial launch back in February 2020.

<!--/excerpt-->

Almost 6 months in the making, we finally released the first all-new component kit for [Tailwind UI](https://tailwindui.com) since the initial launch back in February 2020!

<a href="https://tailwindui.com">
  <img src={card} alt="Tailwind UI Ecommerce available now" />
</a>

[Tailwind UI Ecommerce](https://tailwindui.com/#product-ecommerce) adds over 100 new components across 14 new component categories and 7 new page example categories, including stuff like:

- Product Overviews
- Product Lists
- Category Previews
- Shopping Carts
- Category Filters
- Product Quickviews
- Store Navigation
- Promo Sections
- Checkout Forms
- Customer Reviews
- Order Summaries
- Storefront Pages
- Product Pages
- Order History Pages

...and more.

For a quick preview, check out this product page example we shared via our newsletter last week:

<a href="https://tailwindui.com/page-examples/ecommerce-product-page-02">
  <img
    src={productPagePreview}
    alt="Preview one of the new product page examples"
  />
</a>

It's been a really fun and challenging process putting this together, and I'm really proud of how it turned out. I wrote up a big post about ["Designing Tailwind UI Ecommerce"](https://blog.tailwindcss.com/designing-tailwind-ui-ecommerce) that's worth a read if you're interested in the process behind putting together a new Tailwind UI product like this.

You can check out a bunch more interactive previews as well as screenshots of every single new example over at [the Tailwind UI website](https://tailwindui.com/#product-ecommerce).

If you like what you see, consider [purchasing a license](https://tailwindui.com/pricing) — it's the best way to support our work on Tailwind CSS and Headless UI and makes it possible for us to keep making these tools better and better.


================================================
FILE: src/pages/tailwind-ui-now-with-react-and-vue-support/.prettierrc
================================================
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": false
}


================================================
FILE: src/pages/tailwind-ui-now-with-react-and-vue-support/index.mdx
================================================
import { adamwathan } from '@/authors'
import card from './card.jpg'

export const meta = {
  title: 'Tailwind UI: Now with React + Vue support',
  description: `Last year we released Tailwind UI — a huge directory of professionally designed UI examples built with Tailwind CSS. Today we’re excited to add first class support for React and Vue 3 to all of the examples in Tailwind UI, which makes it even easier to adapt them for your projects.`,
  date: '2021-04-14T16:01:00.000Z',
  authors: [adamwathan],
  image: card,
  footer: `
    <p>
      Want to check it out?
      <a href="https://tailwindui.com" class="font-medium text-teal-600 hover:text-teal-700">
        Visit the Tailwind UI website →
      </a>
    </p>
  `,
}

Last year we released [Tailwind UI](https://tailwindui.com) — a huge directory of professionally designed UI examples built with Tailwind CSS. Up until now, all of the examples in Tailwind UI have been pure HTML which is sort of the lowest common denominator for all web developers, and makes it possible to adapt them to any templating language or JavaScript framework.

Today we’re excited to add first class support for React and Vue 3 to all of the examples in Tailwind UI, which makes it even easier to adapt them for your projects.

<!--more-->

<a href="https://tailwindui.com">
  <img src={card} alt="Tailwind UI: Now for React and Vue" />
</a>

It’s been [a long journey](https://blog.tailwindcss.com/building-react-and-vue-support-for-tailwind-ui) but I am super proud of where we ended up on this one, and really think it’s going to make Tailwind UI a useful tool for a whole new group of Tailwind CSS users.

## Functional and accessible

All of the React and Vue examples in Tailwind UI are powered [Headless UI](https://headlessui.dev) which is a library of components we developed to decouple all of the complicated JS behavior you need to build complex components like modals and dropdowns from the actual styles and markup.

Headless UI handles all of the ARIA attribute management, keyboard interactions, focus handling, and more for you, meaning all of the React and Vue examples provided in Tailwind UI are fully functional, with no need to write any of that complex JS stuff yourself. All of that gnarly complexity is safely tucked away in your `node_modules` folder where we can make improvements and fix bugs on your behalf, without you ever having to change your own code.

## Fully customizable

With Headless UI, we’ve managed to abstract away all of the complicated JS functionality without taking away any control over the actual markup. That means that the entire design is still in entirely under your control.

```jsx
import { useState } from 'react'
import { Switch } from '@headlessui/react'

function classNames(...classes) {
  return classes.filter(Boolean).join(' ')
}

export default function Example() {
  const [enabled, setEnabled] = useState(false)

  return (
    <Switch
      checked={enabled}
      onChange={setEnabled}
      className={classNames(
        enabled ? 'bg-indigo-600' : 'bg-gray-200',
        'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500'
      )}
    >
      <span className="sr-only">Use setting</span>
      <span
        aria-hidden="true"
        className={classNames(
          enabled ? 'translate-x-5' : 'translate-x-0',
          'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200'
        )}
      />
    </Switch>
  )
}
```

You can copy a React or Vue example from Tailwind UI and change absolutely everything about it, from the border radius to the padding to the box shadows to the font-size, all by simply adding utility classes like you’re used to.

## Get started

If you’re already a Tailwind UI customer, all of this stuff is available to you today as a totally free update. Just log in to your account, select between HTML, React, or Vue in the dropdown above any component, and grab the code in the format you want.

If you haven’t checked out Tailwind UI yet, browse the free preview components to get a feel for how it all works. It’s an awesome tool for moving fast on a new side-project idea, finding inspiration for a new feature you need to build at work, or learning how to implement a specific little UI trick with Tailwind, and a great way to support our work on open-source projects like Tailwind CSS, Headless UI, and Heroicons.


================================================
FILE: src/pages/tailwindcss-1-5/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Tailwind CSS v1.5.0',
  description: `Tailwind CSS v1.5.0 is here, now with component variants, responsive container variants, focus-visible support, and more.`,
  date: '2020-07-15T18:55:18.391Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindcss/tailwindcss/discussions/2033',
}

<!--excerpt-->

I was hoping to save v1.5.0 for something _really_ exciting but we needed a new feature to support the new [`@tailwindcss/typography`](https://github.com/tailwindcss/typography) plugin so h\*ck it, we're dropping some new stuff on you early.

No breaking changes, this is a minor release and we're professionals you silly goose.

<!--/excerpt-->

I was hoping to save v1.5.0 for something _really_ exciting but we needed a new feature to support the new [`@tailwindcss/typography`](https://github.com/tailwindcss/typography) plugin so h\*ck it, we're dropping some new stuff on you early.

No breaking changes, this is a minor release and we're professionals you silly goose.

## New Features

### Component `variants` support

Until Tailwind CSS v1.5.0, only "utility" classes were really intended to be used with `variants` (like "responsive", "hover", "focus", etc.)

While these are still much more useful for utilities than any other type of class, we now support generating variants for component classes as well, like the `prose` classes in the new `@tailwindcss/typography` plugin:

```html
<article class="prose md:prose-lg">
  <!-- Content -->
</article>
```

You can take advantage of this feature in your own component classes by using the new `variants` option in the second argument of the `addComponents` plugin API:

```js
plugin(function ({ addComponents })) {
  addComponents({
    '.card': {
      // ...
    }
  }, {
    variants: ['responsive']
  })
})
```

...or using the array shorthand you might be familiar with from the `addUtilities` API:

```js
plugin(function ({ addComponents })) {
  addComponents({
    '.card': {
      // ...
    }
  }, ['responsive'])
})
```

To take advantage of these feature in your custom CSS (rather than using the plugin API), you can use a new `@layer` directive to explicitly tell Tailwind that your styles belong to the "components" bucket:

```css
@layer components {
  @responsive {
    .card {
      /* ... */
    }
  }
}
```

This helps Tailwind purge your unused CSS correctly, ensuring it doesn't remove any responsive component variants when using the default "conservative" purge mode.

### Responsive `container` variants

Piggy-backing off of the new component `variants` support, the `container` class now supports variants!

```html
<!-- Only lock the width at `md` sizes and above -->
<div class="md:container">
  <!-- ... -->
</div>
```

We've enabled responsive variants by default, but if you are sick in the head you can also manually enable other variants like `focus`, `group-hover`, whatever:

```js
// tailwind.config.js
module.exports = {
  // ...
  variants: {
    container: ['responsive', 'focus', 'group-hover'],
  },
}
```

### New `focus-visible` variant

We've added support for the [`:focus-visible` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) using a new `focus-visible` variant.

This is super useful for adding focus styles that _only_ appear to keyboard users, and are ignored for mouse users:

```html
<button class="focus-visible:outline-none focus-visible:shadow-outline ...">
  Click me
</button>
```

It's not enabled for anything by default, but you can enable it in the `variants` section of your config file:

```js
// tailwind.config.js
module.exports = {
  // ...
  variants: {
    backgroundColor: ['responsive', 'hover', 'focus', 'focus-visible'],
  },
}
```

Browser support is still pretty weak on this but getting better. In the mean time, check out the [polyfill](https://github.com/WICG/focus-visible) and corresponding [PostCSS plugin](https://github.com/csstools/postcss-focus-visible) if you'd like to use this in all browsers right away.

### New `checked` variant

We've added a new `checked` variant you can use to conditionally style things like checkboxes and radio buttons:

```html
<input type="checkbox" class="bg-white checked:bg-blue-500" />
```

It's not enabled for anything by default, but you can enable it in the `variants` section of your config file:

```js
// tailwind.config.js
module.exports = {
  // ...
  variants: {
    backgroundColor: ['responsive', 'hover', 'focus', 'checked'],
  },
}
```


================================================
FILE: src/pages/tailwindcss-1-6/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Tailwind CSS v1.6.0',
  description: `Tailwind CSS v1.6.0 is now available, with animations and more!`,
  date: '2020-07-28T16:58:33.714Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2088',
}

It's like Tailwind CSS v1.5 except now there's animation support, overscroll utilities, and more!

There aren't supposed to be any breaking changes here, but I thought that [last time](https://github.com/tailwindlabs/tailwindcss/releases/tag/v1.5.0) too. If I _did_ break something, first person to report it gets a Tailwind shirt.

<!--more-->

## New Features

### Animation support

Tailwind CSS v1.6 adds a brand new `animation` core plugin, with 4 general purpose animations included out of the box:

- `animate-spin`
- `animate-ping`
- `animate-pulse`
- `animate-bounce`

```html
<button type="button" class="bg-indigo-600 ..." disabled>
  <svg class="animate-spin h-5 w-5 mr-3 ..." viewBox="0 0 24 24">
    <!-- ... -->
  </svg>
  Processing
</button>
```

These are completely customizable as always, using the `animation` and `keyframes` sections of your `tailwind.config.js` theme:

```js
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      animation: {
        wiggle: 'wiggle 1s ease-in-out infinite',
      },
      keyframes: {
        wiggle: {
          '0%, 100%': { transform: 'rotate(-3deg)' },
          '50%': { transform: 'rotate(3deg)' },
        },
      },
    },
  },
}
```

For more information and a live demo, [read the new animation documentation](https://tailwindcss.com/docs/animation). For behind the scenes details about the design rationale, [check out the pull request](https://github.com/tailwindlabs/tailwindcss/pull/2068).

### New `prefers-reduced-motion` variants

To go along with the new animation features, we've also added new `motion-safe` and `motion-reduce` variants that allow you to conditionally apply CSS based on the [`prefers-reduced-motion` media feature](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).

These can be useful in conjunction with transition and animation utilities to disable problematic motion for users who are sensitive to it:

```html
<div class="... transition duration-150 ease-in-out motion-reduce:transition-none ..."></div>
```

...or to explicitly opt-in to motion to make sure it's only being shown to users who haven't opted out:

```html
<div class="... motion-safe:transition duration-150 ease-in-out ..."></div>
```

These can be combined with responsive variants and pseudo-class variants as well:

```html
<!-- With responsive variants -->
<div class="sm:motion-reduce:translate-y-0"></div>

<!-- With pseudo-class variants -->
<div class="motion-reduce:hover:translate-y-0"></div>

<!-- With responsive and pseudo-class variants -->
<div class="sm:motion-reduce:hover:translate-y-0"></div>
```

These are currently not enabled for any utilities by default, but you can enabled them as needed in the `variants` section of your `tailwind.config.js` file:

```js
// tailwind.config.js
module.exports = {
  // ...
  variants: {
    translate: ['responsive', 'hover', 'focus', 'motion-safe', 'motion-reduce'],
  },
}
```

For more details, check out [the updated variants documentation](https://tailwindcss.com/docs/pseudo-class-variants).

### New `overscroll-behavior` utilities

We've also added new utilities for the [`overscroll-behavior`](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior) property.

You can use these utilities to control how "scroll chaining" works in your sites, and avoid scrolling the whole page when you reach the top or bottom of an embedded scrollable area.

```html
<div class="overscroll-y-contain ...">
  <!-- ... -->
</button>
```

Note that this is currently **not supported in Safari**, but in my opinion it's not a huge deal to treat this as a progressive enhancement anyways, since it falls back fairly gracefully.

This plugin can be configured in your `tailwind.config.js` file as `overscrollBehavior`:

```js
// tailwind.config.js
module.exports = {
  // ...

  // Disabling the plugin
  corePlugins: {
    overscrollBehavior: false,
  },

  // Customizing the enabled variants
  variants: {
    overscrollBehavior: ['responsive', 'hover'],
  },
}
```

### Generate your CSS without an input file

If you never write any custom CSS and you're sick of creating this file all the time...

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

...then I've got news for you baby — if you're using our `tailwindcss` CLI tool you can start depositing those 58 characters into your savings account instead of wasting them on a pointless CSS file.

The input file argument is now optional in the CLI tool, so if you don't actually _need_ a custom CSS file, you can just write this:

```bash
npx tailwindcss build -o compiled.css
```

Your kids are going to be so grateful for the extra time you get to spend together.


================================================
FILE: src/pages/tailwindcss-1-7/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Tailwind CSS v1.7.0',
  description: `Tailwind CSS v1.7.0 is now available, with gradients and more!`,
  date: '2020-08-18T19:15:00.000Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2183',
}

Another new Tailwind release is here! This time with support for gradients, background-clip, experimental support for using `@apply` with variant utilities, and tons more. Let's dig in!

<!--more-->

## New features

### Gradients

The big one for this release — Tailwind now ships with built-in support for background gradients!

Gradients are designed with a highly composable API that lets you specify up to three color stops in one of 8 directions by default:

```html
<div class="bg-gradient-to-r from-orange-400 via-red-500 to-pink-500">
  <!-- ... -->
</div>
```

![](https://user-images.githubusercontent.com/4323180/90427639-ce57e280-e090-11ea-8611-53a64707fafc.png)

This is made possible by a new `backgroundImage` core plugin (which you can use for any background images you like!) and a new `gradientColorStops` core plugin.

The default configuration for these plugins looks like this:

```js
// tailwind.config.js
module.exports = {
  theme: {
    backgroundImage: {
      'gradient-to-t': 'linear-gradient(to top, var(--gradient-color-stops))',
      'gradient-to-tr': 'linear-gradient(to top right, var(--gradient-color-stops))',
      'gradient-to-r': 'linear-gradient(to right, var(--gradient-color-stops))',
      'gradient-to-br': 'linear-gradient(to bottom right, var(--gradient-color-stops))',
      'gradient-to-b': 'linear-gradient(to bottom, var(--gradient-color-stops))',
      'gradient-to-bl': 'linear-gradient(to bottom left, var(--gradient-color-stops))',
      'gradient-to-l': 'linear-gradient(to left, var(--gradient-color-stops))',
      'gradient-to-tl': 'linear-gradient(to top left, var(--gradient-color-stops))',
    },
    gradientColorStops: (theme) => theme('colors'),
  },
  variants: {
    backgroundImage: ['responsive'],
    gradientColorStops: ['responsive', 'hover', 'focus'],
  },
}
```

Learn more [the original pull request](https://github.com/tailwindlabs/tailwindcss/pull/2176).

### New background-clip utilities

We've also added a new `backgroundClip` core plugin that you can use to control how background are rendered within an element.

It includes 4 new utilities:

| Class             | CSS                            |
| ----------------- | ------------------------------ |
| `bg-clip-border`  | `background-clip: border-box`  |
| `bg-clip-padding` | `background-clip: padding-box` |
| `bg-clip-content` | `background-clip: content-box` |
| `bg-clip-text`    | `background-clip: text`        |

Combined with the new gradient features, you can use this to do cool gradient text stuff like this:

```html
<h1 class="text-6xl font-bold">
  <span class="bg-clip-text text-transparent bg-gradient-to-r from-teal-400 to-blue-500">
    Greetings from Tailwind v1.7.
  </span>
</h1>
```

![](https://user-images.githubusercontent.com/4323180/90427567-b54f3180-e090-11ea-988b-c95ed716866f.png)

Only responsive variants are enabled for the `backgroundClip` plugin by default:

```js
// tailwind.config.js
module.exports = {
  variants: {
    backgroundClip: ['responsive'],
  },
}
```

### New gap utility aliases

For some dumb reason I named the `column-gap` and `row-gap` utilities `col-gap-{n}` and `row-gap-{n}` respectively, which isn't terrible but it's not consistent with how other things in Tailwind are named.

I was finding myself getting them wrong all the time — is `row-gap` the gaps in a row, or the gap between rows?

Tailwind v1.7 introduces new `gap-x-{n}` and `gap-y-{n}` utilities that do the exact same thing but have names that don't suck. They make way more sense than the actual CSS names now that gap for flexbox is starting to roll out too, since flexbox has no "columns".

These utilities will replace the old ones in v2.0, but for now they both exist together.

**We recommend migrating to the new names now, and disabling the old names using this feature flag:**

```js
// tailwind.config.js
module.exports = {
  future: {
    removeDeprecatedGapUtilities: true,
  },
  // ...
}
```

Tailwind will issue a warning in the console to remind you that you are including deprecated classes in your build until you enable this flag.

### New `contents` display utility

We've added a new `contents` class for the recent `display: contents` CSS feature.

```html
<div class="flex">
  <div><!-- ... --></div>
  <!-- This container will act as a phantom container, and its children will be treated as part of the parent flex container -->
  <div class="contents">
    <div><!-- ... --></div>
    <div><!-- ... --></div>
  </div>
  <div><!-- ... --></div>
</div>
```

Learn more about it in [this great article by Rachel Andrew](https://rachelandrew.co.uk/archives/2016/01/29/vanishing-boxes-with-display-contents/).

### Default letter-spacing per font-size

You can now configure a default letter-spacing value for each font-size in your `tailwind.config.js` theme, using a tuple syntax:

```js
// tailwind.config.js
module.exports = {
  theme: {
    fontSize: {
      2xl: ['24px', {
        letterSpacing: '-0.01em',
      }],
      // Or with a default line-height as well
      3xl: ['32px', {
        letterSpacing: '-0.02em',
        lineHeight: '40px',
      }],
    }
  }
}
```

This new syntax is supported in addition to the simpler `[{fontSize}, {lineHeight}]` syntax that was recently introduced.

### Divide border styles

We've added utilities for setting the border style on the `divide` utilities:

```html
<div class="divide-y divide-dashed">
  <div><!-- ... --></div>
  <div><!-- ... --></div>
  <div><!-- ... --></div>
  <div><!-- ... --></div>
</div>
```

These utilities include `responsive` variants by default:

```js
// tailwind.config.js
module.exports = {
  variants: {
    divideStyle: ['responsive'],
  },
}
```

### Access entire config object from plugins

The `config` function passed to the plugin API now returns the entire config option when invoked with no arguments:

```js
tailwind.plugin(function ({ config, addUtilities, /* ... */ })) {
  // Returns entire config object
  config()
})
```

### Define colors as closures

You can now define your colors as callbacks, which receive a bag of parameters you can use to generate your color value.

This is particularly useful when trying to make your custom colors work with the `backgroundOpacity`, `textOpacity`, etc. utilities

```
// tailwind.config.js
module.exports = {
  theme: {
    colors: {
      primary: ({ opacityVariable }) => `rgba(var(--color-primary), var(${variable}, 1))`,
    },
  },
}
```

Currently the only thing passed through is an `opacityVariable` property, which contains the name of the current opacity variable (`--background-opacity`, `--text-opacity`, etc.) depending on which plugin is using the color.

## Deprecations

Tailwind v1.7 introduces a new feature flagging and deprecation system designed to make upgrades as painless as possible.

Any time we deprecate functionality or introduce new (stable) breaking changes, they will be available in Tailwind v1.x under a `future` property in your `tailwind.config.js` file.

Whenever there are deprecations or breaking changes available, Tailwind will warn you in the console on every build until you adopt the new changes and enable the flag in your config file:

```
risk - There are upcoming breaking changes: removeDeprecatedGapUtilities
risk - We highly recommend opting-in to these changes now to simplify upgrading Tailwind in the future.
risk - https://tailwindcss.com/docs/upcoming-changes
```

You can opt-in to a breaking change by setting that flag to `true` in your `tailwind.config.js` file:

```js
// tailwind.config.js
module.exports = {
  future: {
    removeDeprecatedGapUtilities: true,
  },
}
```

If you'd prefer not to opt-in but would like to silence the warning, explicitly set the flag to `false`:

```js
// tailwind.config.js
module.exports = {
  future: {
    removeDeprecatedGapUtilities: false,
  },
}
```

**We do not recommend this**, as it will make upgrading to Tailwind v2.0 more difficult.

### Deprecated gap utilities

As mentioned previously, Tailwind v1.7.0 introduces new `gap-x-{n}` and `gap-y-{n}` utilities to replace the current `col-gap-{n}` and `row-gap-{n}` utilities.

By default both classes will exist, but the old utilities will be removed in Tailwind v2.0.

To migrate to the new class names, simply replace any existing usage of the old names with the new names:

```diff
- <div class="col-gap-4 row-gap-2 ...">
+ <div class="gap-x-4 gap-y-2 ...">
```

To opt-in to the new names now, enable the `removeDeprecatedGapUtilities` flag in your `tailwind.config.js` file:

```js
// tailwind.config.js
module.exports = {
  future: {
    removeDeprecatedGapUtilities: true,
  },
}
```

## Experimental features

Tailwind v1.7.0 introduces a new experimental feature system that allows you to opt-in to new functionality that is coming to Tailwind soon but isn't quite stable yet.

It's important to note that **experimental features may introduce breaking changes, do not follow semver, and can change at any time**.

If you like to live on the wild side though, you can enable all of them like so:

```js
// tailwind.config.js
module.exports = {
  experimental: 'all',
}
```

With that out of the way, here is some of the fun stuff we're working on that we're pumped you can finally play with...

### Use `@apply` with variants and other complex classes

This is a huge one — you can finally use `@apply` with responsive variants, pseudo-class variants, and other complex classes!

```css
.btn {
  @apply bg-indigo hover:bg-indigo-700 sm:text-lg;
}
```

There are a lot of details to understand with this one, so I recommend [reading the pull request](https://github.com/tailwindlabs/tailwindcss/pull/2159) to learn about how it all works.

This introduces breaking changes to how `@apply` worked before, so be sure to read all of the details before just flipping the switch.

To enable this feature, use the `applyComplexClasses` flag:

```js
// tailwind.config.js
module.exports = {
  experimental: {
    applyComplexClasses: true,
  },
}
```

### New color palette

We've added a teaser of the new Tailwind 2.0 color palette that you can start playing with today using the `uniformColorPalette` flag:

```js
// tailwind.config.js
module.exports = {
  experimental: {
    uniformColorPalette: true,
  },
}
```

The idea behind the new palette is that every color at every shade has a similar perceived brightness. So you can swap `indigo-600` with `blue-600` and expect the same color contrast.

We do expect these colors to continue to change a lot as we iterate on them, so use these at your own risk.

### Extended spacing scale

We've added a much bigger spacing scale that includes new micro values like `0.5`, `1.5`, `2.5`, and `3.5`, as well as new large values like `72`, `80`, and `96`, _and_ added percentage based fractional values to the whole spacing scale (`1/2`, `5/6`, `7/12`, etc.)

You can enable the extended spacing scale using the `extendedSpacingScale` flag:

```js
// tailwind.config.js
module.exports = {
  experimental: {
    extendedSpacingScale: true,
  },
}
```

This is pretty stable, I would be surprised if we change this.

### Default line-heights per font-size by default

We've added recommended default line-heights to every built-in font-size, which can be enabled using the `defaultLineHeights` flag:

```js
// tailwind.config.js
module.exports = {
  experimental: {
    defaultLineHeights: true,
  },
}
```

This is a breaking change and will impact your designs, as previously all font sizes had a default line-height of `1.5`.

### Extended font size scale

We've added three new font sizes (`7xl`, `8xl`, and `9xl`) to keep up with the latest huge-as-hell-hero-text trends. They include default line-heights as well.

You can enable them under the `extendedFontSizeScale` flag:

```js
// tailwind.config.js
module.exports = {
  experimental: {
    extendedFontSizeScale: true,
  },
}
```


================================================
FILE: src/pages/tailwindcss-1-8/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Tailwind CSS v1.8.0',
  description: `Tailwind CSS v1.8.0 is now available, with font-variant-numeric support, experimental dark mode, and more!`,
  date: '2020-09-04T20:15:00.000Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2315',
}

Tailwind CSS v1.8 is now available with a handful of new utilities, a couple new features, and an exciting new experiment!

<!--more-->

### New features

- New `font-variant-numeric` utilities ([#2305](https://github.com/tailwindlabs/tailwindcss/pull/2305))
- New `place-items`, `place-content`, `place-self`, `justify-items`, and `justify-self` utilities ([#2306](https://github.com/tailwindlabs/tailwindcss/pull/2306))
- New `preserveHtmlElements` option for `purge` ([#2283](https://github.com/tailwindlabs/tailwindcss/pull/2283))
- New `layers` mode for `purge` ([#2288](https://github.com/tailwindlabs/tailwindcss/pull/2288))
- Support configuring variants as functions ([#2309](https://github.com/tailwindlabs/tailwindcss/pull/2309))
- Dark mode variant (experimental) ([#2279](https://github.com/tailwindlabs/tailwindcss/pull/2279))

### Changes

- CSS within `@layer` at-rules are now grouped with the corresponding `@tailwind` at-rule ([#2312](https://github.com/tailwindlabs/tailwindcss/pull/2312))

### Deprecations

- The `conservative` purge mode has been deprecated in favor of the new `layers` mode ([#2288](https://github.com/tailwindlabs/tailwindcss/pull/2288))

Check out the [full release notes on GitHub](https://github.com/tailwindlabs/tailwindcss/releases/tag/v1.8.0) for more details.


================================================
FILE: src/pages/tailwindcss-1-9/.prettierrc
================================================
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": false
}


================================================
FILE: src/pages/tailwindcss-1-9/index.mdx
================================================
import { adamwathan } from '@/authors'
import image from './card.jpg'

export const meta = {
  title: 'Tailwind CSS v1.9.0',
  description: `We just released Tailwind CSS v1.9 which adds support for configuration presets, useful new CSS grid utilities, extended border radius, rotate, and skew scales, helpful accessibility improvements, and more!`,
  date: '2020-10-13T18:30:00.000Z',
  authors: [adamwathan],
  image,
  discussion: 'https://github.com/tailwindlabs/tailwindcss/discussions/2552',
}

We just released Tailwind CSS v1.9 which adds support for configuration presets, useful new CSS grid utilities, extended border radius, rotate, and skew scales, helpful accessibility improvements, and more!

<!--more-->

Let's dig in to the highlights...

- [Configuration presets](#configuration-presets)
- [Utilities for `grid-auto-columns` and `grid-auto-rows`](#utilities-for-grid-auto-columns-and-grid-auto-rows)
- [Focus indicator improvements and configurable outlines](#focus-indicator-improvements-and-configurable-outlines)
- [Extended border radius, rotate, and skew scales](#extended-border-radius-rotate-and-skew-scales)
- [Upgrading to v1.9](#upgrading)

For the complete summary of changes [check out the release notes on GitHub](https://github.com/tailwindlabs/tailwindcss/releases/tag/v1.9.0).

---

<h2 id="configuration-presets">Configuration presets</h2>

Tailwind CSS v1.9 adds a new `presets` key to the `tailwind.config.js` file that makes it possible to configure a custom "base configuration" for your projects.

```js
// tailwind.config.js
module.exports = {
  presets: [require('@my-company/tailwind-base')],
  theme: {
    extend: {
      // Project specific overrides...
    },
  },
}
```

Whatever you provide under `presets` _replaces_ the default Tailwind base configuration, so you can define your own totally custom starting point. This is really helpful if you're part of a team that works on multiple different Tailwind projects that all need to share the same brand colors, font customizations, or spacing scale.

You can even list multiple presets, which are merged together from top to bottom:

```js
// tailwind.config.js
module.exports = {
  presets: [
    require('@my-company/tailwind-base'),
    require('@my-company/tailwind-marketing'),
  ],
  theme: {
    extend: {
      // Project specific overrides...
    },
  },
}
```

The logic to merge your project-specific configuration with your custom base configuration is exactly the same as how things work with the default configuration, so all of the features you're used to like `extend` still work exactly the way you'd expect.

---

<h2 id="utilities-for-grid-auto-columns-and-grid-auto-rows">
  Utilities for grid-auto-columns and grid-auto-rows
</h2>

We've added new `gridAutoColumns` and `gridAutoRows` core plugins that add new utilities for the `grid-auto-columns` and `grid-auto-rows` CSS properties respectively.

These utilities let you control the size of implicitly-created grid columns and rows. Use them to set a default column/row size whenever you don't specify a number of columns/rows for your grid.

```html
<div class="grid grid-flow-col auto-cols-max">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>
```

Here's a list of the new utilities that are included out of the box:

| Class            | CSS                                  |
| ---------------- | ------------------------------------ |
| `auto-cols-auto` | `grid-auto-columns: auto;`           |
| `auto-cols-min`  | `grid-auto-columns: min-content;`    |
| `auto-cols-max`  | `grid-auto-columns: max-content;`    |
| `auto-cols-fr`   | `grid-auto-columns: minmax(0, 1fr);` |
| `auto-rows-auto` | `grid-auto-rows: auto;`              |
| `auto-rows-min`  | `grid-auto-rows: min-content;`       |
| `auto-rows-max`  | `grid-auto-rows: max-content;`       |
| `auto-rows-fr`   | `grid-auto-rows: minmax(0, 1fr);`    |

We include `responsive` variants for these utilities by default, and they can be configured just like you'd expect under the `gridAutoColumns` and `gridAutoRows` sections of your `tailwind.config.js` file.

---

<h2 id="focus-indicator-improvements-and-configurable-outlines">
  Focus indicator improvements and configurable outlines
</h2>

We've updated the `outline-none` class to render a _transparent_ out
Download .txt
gitextract_xwaefze3/

├── .gitignore
├── README.md
├── jsconfig.json
├── next.config.js
├── package.json
├── postcss.config.js
├── prettier.config.js
├── public/
│   ├── browserconfig.xml
│   └── site.webmanifest
├── remark/
│   ├── vendor/
│   │   └── prism-diff-highlight.js
│   ├── withProse.js
│   └── withSyntaxHighlighting.js
├── scripts/
│   └── build-rss.js
├── src/
│   ├── authors.js
│   ├── components/
│   │   ├── Header.js
│   │   ├── PageTitle.js
│   │   ├── Post.js
│   │   └── SectionContainer.js
│   ├── css/
│   │   ├── prism.css
│   │   └── tailwind.css
│   ├── getAllPostPreviews.js
│   ├── getStaticProps.js
│   └── pages/
│       ├── _app.js
│       ├── _document.js
│       ├── building-react-and-vue-support-for-tailwind-ui/
│       │   └── index.mdx
│       ├── building-the-tailwind-blog/
│       │   └── index.mdx
│       ├── designing-tailwind-ui-ecommerce/
│       │   └── index.mdx
│       ├── from-900-to-1-how-we-hired-robin-malfait/
│       │   └── index.mdx
│       ├── headless-ui-unstyled-accessible-ui-components/
│       │   └── index.mdx
│       ├── headless-ui-v1/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── headless-ui-v1-4/
│       │   ├── .prettierrc
│       │   ├── index.mdx
│       │   └── snippets/
│       │       ├── react-1.mdx
│       │       ├── react-2.mdx
│       │       ├── react-3.mdx
│       │       ├── react-4.mdx
│       │       ├── vue-1.mdx
│       │       ├── vue-2.mdx
│       │       ├── vue-3.mdx
│       │       └── vue-4.mdx
│       ├── heroicons-v1/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── index.js
│       ├── introducing-heroicons/
│       │   └── index.mdx
│       ├── introducing-linting-for-tailwindcss-intellisense/
│       │   └── index.mdx
│       ├── introducing-tailwind-play/
│       │   └── index.mdx
│       ├── just-in-time-the-next-generation-of-tailwind-css/
│       │   └── index.mdx
│       ├── multi-line-truncation-with-tailwindcss-line-clamp/
│       │   └── index.mdx
│       ├── simon-vrachliotis-joins-tailwind-labs/
│       │   └── index.mdx
│       ├── tailwind-ui-ecommerce/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwind-ui-now-with-react-and-vue-support/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-1-5/
│       │   └── index.mdx
│       ├── tailwindcss-1-6/
│       │   └── index.mdx
│       ├── tailwindcss-1-7/
│       │   └── index.mdx
│       ├── tailwindcss-1-8/
│       │   └── index.mdx
│       ├── tailwindcss-1-9/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-2-1/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-2-2/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── tailwindcss-from-zero-to-production/
│       │   └── index.mdx
│       ├── tailwindcss-typography/
│       │   └── index.mdx
│       ├── tailwindcss-v2/
│       │   ├── .prettierrc
│       │   └── index.mdx
│       ├── utility-friendly-transitions-with-tailwindui-react/
│       │   └── index.mdx
│       ├── welcoming-brad-cornes-to-the-tailwind-team/
│       │   └── index.mdx
│       ├── welcoming-david-luhr-to-tailwind-labs/
│       │   └── index.mdx
│       ├── welcoming-james-mcdonald-to-tailwind-labs/
│       │   └── index.mdx
│       └── whats-new-in-tailwindcss-on-youtube/
│           └── index.mdx
└── tailwind.config.js
Download .txt
SYMBOL INDEX (19 symbols across 10 files)

FILE: remark/withSyntaxHighlighting.js
  function highlightCode (line 7) | function highlightCode(code, prismLanguage) {

FILE: src/components/Header.js
  function TailwindMark (line 3) | function TailwindMark({ className }) {
  function TailwindLogo (line 17) | function TailwindLogo({ className }) {
  function Header (line 38) | function Header() {

FILE: src/components/PageTitle.js
  function PageTitle (line 1) | function PageTitle({ children }) {

FILE: src/components/Post.js
  function Post (line 13) | function Post({ meta, children, posts }) {

FILE: src/components/SectionContainer.js
  function SectionContainer (line 1) | function SectionContainer({ children }) {

FILE: src/getAllPostPreviews.js
  function importAll (line 1) | function importAll(r) {
  function dateSortDesc (line 8) | function dateSortDesc(a, b) {
  function getAllPostPreviews (line 14) | function getAllPostPreviews() {
  function getAllPosts (line 21) | function getAllPosts() {

FILE: src/getStaticProps.js
  function getStaticProps (line 3) | async function getStaticProps() {

FILE: src/pages/_app.js
  function App (line 4) | function App({ Component, pageProps }) {

FILE: src/pages/_document.js
  class InlineStylesHead (line 5) | class InlineStylesHead extends Head {
    method getCssLinks (line 6) | getCssLinks(files) {
  class Document (line 26) | class Document extends NextDocument {
    method getInitialProps (line 27) | static async getInitialProps(ctx) {
    method render (line 32) | render() {

FILE: src/pages/index.js
  function Home (line 13) | function Home() {
Condensed preview — 74 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (276K chars).
[
  {
    "path": ".gitignore",
    "chars": 362,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "README.md",
    "chars": 1206,
    "preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
  },
  {
    "path": "jsconfig.json",
    "chars": 109,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"node_modules\",\n    \"paths\": {\n      \"@/*\": [\"../src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "next.config.js",
    "chars": 2618,
    "preview": "const { createLoader } = require('simple-functional-loader')\nconst withBundleAnalyzer = require('@next/bundle-analyzer')"
  },
  {
    "path": "package.json",
    "chars": 822,
    "preview": "{\n  \"name\": \"tailwind-blog\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\":"
  },
  {
    "path": "postcss.config.js",
    "chars": 82,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "prettier.config.js",
    "chars": 157,
    "preview": "module.exports = {\n  semi: false,\n  singleQuote: true,\n  printWidth: 100,\n  tabWidth: 2,\n  useTabs: false,\n  trailingCom"
  },
  {
    "path": "public/browserconfig.xml",
    "chars": 246,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo"
  },
  {
    "path": "public/site.webmanifest",
    "chars": 426,
    "preview": "{\n    \"name\": \"\",\n    \"short_name\": \"\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n     "
  },
  {
    "path": "remark/vendor/prism-diff-highlight.js",
    "chars": 2714,
    "preview": "// https://github.com/PrismJS/prism/blob/master/plugins/diff-highlight/prism-diff-highlight.js\nmodule.exports = (Prism) "
  },
  {
    "path": "remark/withProse.js",
    "chars": 969,
    "preview": "const proseComponents = ['Heading']\n\nconst isJsNode = (node) => {\n  return (\n    ['jsx', 'import', 'export'].includes(no"
  },
  {
    "path": "remark/withSyntaxHighlighting.js",
    "chars": 1258,
    "preview": "const visit = require('unist-util-visit')\nconst Prism = require('prismjs')\nconst loadLanguages = require('prismjs/compon"
  },
  {
    "path": "scripts/build-rss.js",
    "chars": 1966,
    "preview": "import fs from 'fs'\nimport ReactDOMServer from 'react-dom/server'\nimport { MDXProvider } from '@mdx-js/react'\nimport { F"
  },
  {
    "path": "src/authors.js",
    "chars": 805,
    "preview": "import adamwathanAvatar from './img/adamwathan.jpg'\nimport bradlcAvatar from './img/bradlc.jpg'\nimport steveschogerAvata"
  },
  {
    "path": "src/components/Header.js",
    "chars": 4850,
    "preview": "import Link from 'next/link'\n\nexport function TailwindMark({ className }) {\n  return (\n    <svg className={className} fi"
  },
  {
    "path": "src/components/PageTitle.js",
    "chars": 245,
    "preview": "export default function PageTitle({ children }) {\n  return (\n    <h1 className=\"text-3xl font-extrabold text-gray-900 tr"
  },
  {
    "path": "src/components/Post.js",
    "chars": 10722,
    "preview": "import Head from 'next/head'\nimport PageTitle from '@/components/PageTitle'\nimport tinytime from 'tinytime'\nimport Link "
  },
  {
    "path": "src/components/SectionContainer.js",
    "chars": 154,
    "preview": "export default function SectionContainer({ children }) {\n  return <div className=\"max-w-3xl mx-auto px-4 sm:px-6 xl:max-"
  },
  {
    "path": "src/css/prism.css",
    "chars": 1157,
    "preview": ".language-javascript {\n  color: white;\n}\n\n.token.tag,\n.token.class-name,\n.token.selector,\n.token.selector .class,\n.token"
  },
  {
    "path": "src/css/tailwind.css",
    "chars": 138,
    "preview": "/* purgecss start ignore */\n@tailwind base;\n/* purgecss end ignore */\n\n@tailwind components;\n@tailwind utilities;\n\n@impo"
  },
  {
    "path": "src/getAllPostPreviews.js",
    "chars": 821,
    "preview": "function importAll(r) {\n  return r.keys().map((fileName) => ({\n    link: fileName.substr(1).replace(/\\/index\\.mdx$/, '')"
  },
  {
    "path": "src/getStaticProps.js",
    "chars": 259,
    "preview": "import getAllPostPreviews from '@/getAllPostPreviews'\n\nexport async function getStaticProps() {\n  return {\n    props: {\n"
  },
  {
    "path": "src/pages/_app.js",
    "chars": 1030,
    "preview": "import '@/css/tailwind.css'\nimport Head from 'next/head'\n\nexport default function App({ Component, pageProps }) {\n  retu"
  },
  {
    "path": "src/pages/_document.js",
    "chars": 1305,
    "preview": "import NextDocument, { Html, Head, Main, NextScript } from 'next/document'\nimport * as fs from 'fs'\nimport * as path fro"
  },
  {
    "path": "src/pages/building-react-and-vue-support-for-tailwind-ui/index.mdx",
    "chars": 30180,
    "preview": "import { adamwathan } from '@/authors'\nimport stackedListExample from './stacked-list-example.png'\n\nexport const meta = "
  },
  {
    "path": "src/pages/building-the-tailwind-blog/index.mdx",
    "chars": 12660,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Building the Tail"
  },
  {
    "path": "src/pages/designing-tailwind-ui-ecommerce/index.mdx",
    "chars": 12084,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\n\nexport const meta = {\n  private: true,\n  title: 'D"
  },
  {
    "path": "src/pages/from-900-to-1-how-we-hired-robin-malfait/index.mdx",
    "chars": 12914,
    "preview": "import { adamwathan } from '@/authors'\nimport cardImage from './card.jpg'\nimport robinImage from './robin.jpg'\n\nexport c"
  },
  {
    "path": "src/pages/headless-ui-unstyled-accessible-ui-components/index.mdx",
    "chars": 8983,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\nimport headlessUiCard from './headless-ui-card.svg"
  },
  {
    "path": "src/pages/headless-ui-v1/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/headless-ui-v1/index.mdx",
    "chars": 6976,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\n\nexport const meta = {\n  title: 'Headless UI v1.0',"
  },
  {
    "path": "src/pages/headless-ui-v1-4/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/headless-ui-v1-4/index.mdx",
    "chars": 7701,
    "preview": "import { adamwathan, robinmalfait } from '@/authors'\nimport card from './card.jpg'\nimport banner from './banner.jpg'\nimp"
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/react-1.mdx",
    "chars": 394,
    "preview": "```jsx\nimport { Tab } from '@headlessui/react'\n\nfunction MyTabs() {\n  return (\n    <Tab.Group>\n      <Tab.List>\n        "
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/react-2.mdx",
    "chars": 397,
    "preview": "```jsx\nimport { Disclosure } from '@headlessui/react'\nimport MyLink from './MyLink'\n\nfunction MyDisclosure() {\n  return "
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/react-3.mdx",
    "chars": 368,
    "preview": "```jsx\nimport { Popover } from '@headlessui/react'\nimport MyLink from './MyLink'\n\nfunction MyPopover() {\n  return (\n    "
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/react-4.mdx",
    "chars": 465,
    "preview": "```jsx\nimport { Popover } from '@headlessui/react'\n\nfunction MyPopover() {\n  return (\n    <Popover>\n      <Popover.Butto"
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/vue-1.mdx",
    "chars": 528,
    "preview": "```html\n<template>\n  <TabGroup>\n    <TabList>\n      <Tab>Tab 1</Tab>\n      <Tab>Tab 2</Tab>\n      <Tab>Tab 3</Tab>\n    <"
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/vue-2.mdx",
    "chars": 510,
    "preview": "```html\n<template>\n  <Disclosure>\n    <DisclosureButton>Open mobile menu</DisclosureButton>\n    <DisclosurePanel>\n      "
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/vue-3.mdx",
    "chars": 455,
    "preview": "```html\n<template>\n  <Popover>\n    <PopoverButton>Solutions</PopoverButton>\n\n    <PopoverPanel>\n      <PopoverButton :as"
  },
  {
    "path": "src/pages/headless-ui-v1-4/snippets/vue-4.mdx",
    "chars": 568,
    "preview": "```html\n<template>\n  <Popover>\n    <PopoverButton>Solutions</PopoverButton>\n\n    <PopoverPanel v-slot=\"{ close }\">\n     "
  },
  {
    "path": "src/pages/heroicons-v1/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/heroicons-v1/index.mdx",
    "chars": 1984,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\n\nexport const meta = {\n  title: 'Heroicons v1.0',\n "
  },
  {
    "path": "src/pages/index.js",
    "chars": 4019,
    "preview": "import tinytime from 'tinytime'\nimport Link from 'next/link'\nimport Head from 'next/head'\nimport getAllPostPreviews from"
  },
  {
    "path": "src/pages/introducing-heroicons/index.mdx",
    "chars": 2808,
    "preview": "import { steveschoger } from '@/authors'\nimport card from './card.jpg'\nimport banner from './banner.svg'\nimport iconStyl"
  },
  {
    "path": "src/pages/introducing-linting-for-tailwindcss-intellisense/index.mdx",
    "chars": 3797,
    "preview": "import { bradlc } from '@/authors'\nimport imgCss from './css.png'\nimport imgCss2x from './css@2x.png'\nimport imgHtml fro"
  },
  {
    "path": "src/pages/introducing-tailwind-play/index.mdx",
    "chars": 2985,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: `Introducing Tailw"
  },
  {
    "path": "src/pages/just-in-time-the-next-generation-of-tailwind-css/index.mdx",
    "chars": 4704,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: `Just-In-Time: The"
  },
  {
    "path": "src/pages/multi-line-truncation-with-tailwindcss-line-clamp/index.mdx",
    "chars": 3457,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Multi-line trunca"
  },
  {
    "path": "src/pages/simon-vrachliotis-joins-tailwind-labs/index.mdx",
    "chars": 2407,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Simon Vrachliotis"
  },
  {
    "path": "src/pages/tailwind-ui-ecommerce/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/tailwind-ui-ecommerce/index.mdx",
    "chars": 2714,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\nimport productPagePreview from './product-page-prev"
  },
  {
    "path": "src/pages/tailwind-ui-now-with-react-and-vue-support/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/tailwind-ui-now-with-react-and-vue-support/index.mdx",
    "chars": 4602,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind UI: Now w"
  },
  {
    "path": "src/pages/tailwindcss-1-5/index.mdx",
    "chars": 4596,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v1.5"
  },
  {
    "path": "src/pages/tailwindcss-1-6/index.mdx",
    "chars": 5083,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v1.6"
  },
  {
    "path": "src/pages/tailwindcss-1-7/index.mdx",
    "chars": 12279,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v1.7"
  },
  {
    "path": "src/pages/tailwindcss-1-8/index.mdx",
    "chars": 1710,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v1.8"
  },
  {
    "path": "src/pages/tailwindcss-1-9/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/tailwindcss-1-9/index.mdx",
    "chars": 7842,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v1.9"
  },
  {
    "path": "src/pages/tailwindcss-2-1/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/tailwindcss-2-1/index.mdx",
    "chars": 4094,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v2.1"
  },
  {
    "path": "src/pages/tailwindcss-2-2/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/tailwindcss-2-2/index.mdx",
    "chars": 21269,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Tailwind CSS v2.2"
  },
  {
    "path": "src/pages/tailwindcss-from-zero-to-production/index.mdx",
    "chars": 2403,
    "preview": "import { simonswiss } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: `\"Tailwind CSS: Fr"
  },
  {
    "path": "src/pages/tailwindcss-typography/index.mdx",
    "chars": 3408,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Introducing Tailw"
  },
  {
    "path": "src/pages/tailwindcss-v2/.prettierrc",
    "chars": 63,
    "preview": "{\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "src/pages/tailwindcss-v2/index.mdx",
    "chars": 18182,
    "preview": "import { adamwathan } from '@/authors'\nimport image from './card.jpg'\nimport colorsImage from './announcement-tailwind-c"
  },
  {
    "path": "src/pages/utility-friendly-transitions-with-tailwindui-react/index.mdx",
    "chars": 3776,
    "preview": "import { robinmalfait } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: 'Utility-Friendl"
  },
  {
    "path": "src/pages/welcoming-brad-cornes-to-the-tailwind-team/index.mdx",
    "chars": 2423,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\nimport intellisenseImage from '../introducing-linti"
  },
  {
    "path": "src/pages/welcoming-david-luhr-to-tailwind-labs/index.mdx",
    "chars": 4427,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\n\nexport const meta = {\n  title: 'Welcoming David Lu"
  },
  {
    "path": "src/pages/welcoming-james-mcdonald-to-tailwind-labs/index.mdx",
    "chars": 2770,
    "preview": "import { adamwathan } from '@/authors'\nimport card from './card.jpg'\nimport jamesWorkExamples from './james-work.jpg'\nim"
  },
  {
    "path": "src/pages/whats-new-in-tailwindcss-on-youtube/index.mdx",
    "chars": 2517,
    "preview": "import { simonswiss } from '@/authors'\nimport image from './card.jpg'\n\nexport const meta = {\n  title: `\"What's new in Ta"
  },
  {
    "path": "tailwind.config.js",
    "chars": 2592,
    "preview": "const defaultTheme = require('tailwindcss/defaultTheme')\nconst colors = require('tailwindcss/colors')\nconst mdx = requir"
  }
]

About this extraction

This page contains the full source code of the tailwindlabs/blog.tailwindcss.com GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 74 files (256.9 KB), approximately 67.0k tokens, and a symbol index with 19 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!