3996 lines
index.js
import { Testimonials } from '@/components/Testimonials' import { DarkMode } from '@/components/home/DarkMode' import { ConstraintBased } from '@/components/home/ConstraintBased' import { BuildAnything } from '@/components/home/BuildAnything' import { Performance } from '@/components/home/Performance' import { MobileFirst } from '@/components/home/MobileFirst' import { StateVariants } from '@/components/home/StateVariants' import { ComponentDriven } from '@/components/home/ComponentDriven' import { Customization } from '@/components/home/Customization' import { ModernFeatures } from '@/components/home/ModernFeatures' import { EditorTools } from '@/components/home/EditorTools' import { ReadyMadeComponents } from '@/components/home/ReadyMadeComponents' import { SearchButton } from '@/components/Search' import { Hero } from '@/components/home/Hero' import { Logo } from '@/components/Logo' import { Footer } from '@/components/home/Footer' import NextLink from 'next/link' import Head from 'next/head' import { NavItems, NavPopover } from '@/components/Header' import styles from './index.module.css' import clsx from 'clsx' import { ThemeToggle } from '@/components/ThemeToggle' import socialCardLarge from '@/img/social-card-large.jpg' function Header() { return ( <header className="relative"> <div className="px-4 sm:px-6 md:px-8"> <div className={clsx( 'absolute inset-0 bottom-10 bg-bottom bg-no-repeat bg-slate-50 dark:bg-[#0B1120]', styles.beams )} > <div className="absolute inset-0 bg-grid-slate-900/[0.04] bg-[bottom_1px_center] dark:bg-grid-slate-400/[0.05] dark:bg-bottom dark:border-b dark:border-slate-100/5" style={{ maskImage: 'linear-gradient(to bottom, transparent, black)', WebkitMaskImage: 'linear-gradient(to bottom, transparent, black)', }} /> </div> <div className="relative pt-6 lg:pt-8 flex items-center justify-between text-slate-700 font-semibold text-sm leading-6 dark:text-slate-200"> <Logo className="w-auto h-5" /> <div className="flex items-center"> <SearchButton className="text-slate-500 hover:text-slate-600 w-8 h-8 -my-1 flex items-center justify-center md:hidden dark:hover:text-slate-300"> <span className="sr-only">Search</span> <svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" > <path d="m19 19-3.5-3.5" /> <circle cx="11" cy="11" r="6" /> </svg> </SearchButton> <NavPopover className="-my-1 ml-2 -mr-1" display="md:hidden" /> <div className="hidden md:flex items-center"> <nav> <ul className="flex items-center gap-x-8"> <NavItems /> </ul> </nav> <div className="flex items-center border-l border-slate-200 ml-6 pl-6 dark:border-slate-800"> <ThemeToggle /> <a href="https://github.com/tailwindlabs/tailwindcss" className="ml-6 block text-slate-400 hover:text-slate-500 dark:hover:text-slate-300" > <span className="sr-only">Tailwind CSS on GitHub</span> <svg viewBox="0 0 16 16" className="w-5 h-5" fill="currentColor" aria-hidden="true" > <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" /> </svg> </a> </div> </div> </div> </div> <div className="relative max-w-5xl mx-auto pt-20 sm:pt-24 lg:pt-32"> <h1 className="text-slate-900 font-extrabold text-4xl sm:text-5xl lg:text-6xl tracking-tight text-center dark:text-white"> Rapidly build modern websites without ever leaving your HTML. </h1> <p className="mt-6 text-lg text-slate-600 text-center max-w-3xl mx-auto dark:text-slate-400"> A utility-first CSS framework packed with classes like{' '} <code className="font-mono font-medium text-sky-500 dark:text-sky-400">flex</code>,{' '} <code className="font-mono font-medium text-sky-500 dark:text-sky-400">pt-4</code>,{' '} <code className="font-mono font-medium text-sky-500 dark:text-sky-400"> text-center </code>{' '} and{' '} <code className="font-mono font-medium text-sky-500 dark:text-sky-400">rotate-90</code>{' '} that can be composed to build any design, directly in your markup. </p> <div className="mt-6 sm:mt-10 flex justify-center space-x-6 text-sm"> <NextLink href="/docs/installation"> <a className="bg-slate-900 hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto dark:bg-sky-500 dark:highlight-white/20 dark:hover:bg-sky-400"> Get started </a> </NextLink> <SearchButton className="hidden sm:flex items-center w-72 text-left space-x-3 px-4 h-12 bg-white ring-1 ring-slate-900/10 hover:ring-slate-300 focus:outline-none focus:ring-2 focus:ring-sky-500 shadow-sm rounded-lg text-slate-400 dark:bg-slate-800 dark:ring-0 dark:text-slate-300 dark:highlight-white/5 dark:hover:bg-slate-700"> {({ actionKey }) => ( <> <svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="flex-none text-slate-300 dark:text-slate-400" aria-hidden="true" > <path d="m19 19-3.5-3.5" /> <circle cx="11" cy="11" r="6" /> </svg> <span className="flex-auto">Quick search...</span> {actionKey && ( <kbd className="font-sans font-semibold dark:text-slate-500"> <abbr title={actionKey[1]} className="no-underline text-slate-300 dark:text-slate-500" > {actionKey[0]} </abbr>{' '} K </kbd> )} </> )} </SearchButton> </div> </div> </div> <Hero /> </header> ) } export default function Home() { return ( <> <Head> <meta key="twitter:title" name="twitter:title" content="Tailwind CSS - Rapidly build modern websites without ever leaving your HTML." /> <meta key="og:title" property="og:title" content="Tailwind CSS - Rapidly build modern websites without ever leaving your HTML." /> <title>Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.</title> </Head> <div className="mb-20 overflow-hidden sm:mb-32 md:mb-40"> <Header /> <section className="text-center px-8 mt-20 sm:mt-32 md:mt-40"> <h2 className="text-slate-900 text-4xl tracking-tight font-extrabold sm:text-5xl dark:text-white"> “Best practices” don’t actually work. </h2> <figure> <blockquote> <p className="mt-6 max-w-3xl mx-auto text-lg"> I’ve written{' '} <a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/" className="text-sky-500 font-semibold dark:text-sky-400" > a few thousand words </a>{' '} on why traditional “semantic class names” are the reason CSS is hard to maintain, but the truth is you’re never going to believe me until you actually try it. If you can suppress the urge to retch long enough to give it a chance, I really think you’ll wonder how you ever worked with CSS any other way. </p> </blockquote> <figcaption className="mt-6 flex items-center justify-center space-x-4 text-left"> <img src={require('@/img/adam.jpg').default.src} alt="" className="w-14 h-14 rounded-full" loading="lazy" decoding="async" /> <div> <div className="text-slate-900 font-semibold dark:text-white">Adam Wathan</div> <div className="mt-0.5 text-sm leading-6">Creator of Tailwind CSS</div> </div> </figcaption> </figure> </section> </div> <Testimonials /> <div className="pt-20 mb-20 flex flex-col gap-y-20 overflow-hidden sm:pt-32 sm:mb-32 sm:gap-y-32 md:pt-40 md:mb-40 md:gap-y-40"> <ConstraintBased /> <BuildAnything /> <Performance /> <MobileFirst /> <StateVariants /> <ComponentDriven /> <DarkMode /> <Customization /> <ModernFeatures /> <EditorTools /> <ReadyMadeComponents /> </div> <Footer /> </> ) } Home.layoutProps = { meta: { ogImage: socialCardLarge.src, }, }
ConstraintBased.js
import { IconContainer, Caption, BigText, Paragraph, Link, Widont } from '@/components/home/common' import { Tabs } from '@/components/Tabs' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import defaultConfig from 'defaultConfig' import { AnimatePresence, motion } from 'framer-motion' import { useState } from 'react' import { GridLockup } from '../GridLockup' import clsx from 'clsx' import { lines as sizingSample } from '../../samples/sizing.html?highlight' import { lines as colorsSample } from '../../samples/colors.html?highlight' import { lines as typographySample } from '../../samples/typography.html?highlight' import { lines as shadowsSample } from '../../samples/shadows.html?highlight' const tokens = { Sizing: sizingSample, Colors: colorsSample, Typography: typographySample, Shadows: shadowsSample, } let tabs = { Sizing: (selected) => ( <> <rect x="5" y="5" width="28" height="28" rx="4" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <path d="M5 41h28M33 39v4M5 39v4M39 5h4M39 33h4M41 33V5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </> ), Colors: (selected) => ( <> <path d="M17.687 42.22 40.57 29.219a4 4 0 0 0 1.554-5.36L39 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /> <path d="M27.477 7.121a1 1 0 1 0-.954 1.758l.954-1.758Zm5.209 3.966.477-.879-.477.88Zm1.555 5.515-.866-.5-.003.006.87.494ZM26.523 8.88l5.686 3.087.954-1.758-5.686-3.087-.954 1.758Zm6.849 7.23-12.616 22.21 1.738.987 12.617-22.21-1.74-.988Zm-1.163-4.143a3 3 0 0 1 1.166 4.136l1.732 1a5 5 0 0 0-1.944-6.894l-.954 1.758Z" fill="currentColor" /> <path d="M5 9a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v25a9 9 0 1 1-18 0V9Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <circle cx="14" cy="34" r="3" fill={selected ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" /> </> ), Typography: (selected) => ( <> <path d="M5 13a8 8 0 0 1 8-8h22a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H13a8 8 0 0 1-8-8V13Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <path d="M15.5 25h9M13 31l5.145-12.748c.674-1.67 3.036-1.67 3.71 0L27 31" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M31 13s2 0 2 1.833v18.334C33 35 31 35 31 35M35 13s-2 0-2 1.833v18.334C33 35 35 35 35 35M31 24h4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /> </> ), Shadows: (selected) => ( <> <path d="M24 43c10.493 0 19-8.507 19-19S34.493 5 24 5m-4 .422C11.427 7.259 5 14.879 5 24c0 9.121 6.427 16.741 15 18.578" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /> <path d="M24 42.819V5.181c0-.1.081-.181.181-.181C34.574 5 43 13.607 43 24c0 10.394-8.426 19-18.819 19a.181.181 0 0 1-.181-.181Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" strokeLinejoin="round" /> <path d="M28 10h3M28 14h7M28 18h10M28 22h11M28 26h10M28 30h9M28 34h7M28 38h3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /> </> ), } function Bars({ sizes, className }) { return ( <motion.ul exit={{ opacity: 0 }} className={clsx('relative font-mono text-xs pt-6 space-y-4', className)} > {sizes.map((key, i) => ( <li key={key}> <motion.div className="h-6 origin-left bg-white shadow ring-1 ring-slate-700/5 px-1 flex items-center dark:bg-indigo-500 dark:text-white dark:highlight-white/10" style={{ width: defaultConfig.theme.width[key], borderRadius: 4 }} initial={{ scaleX: 0 }} animate={{ scaleX: 1 }} transition={{ delay: i * 0.1, damping: 100 }} > <div className="flex-none w-0.5 h-1 bg-slate-300 dark:bg-white" /> <span className="flex-auto text-center">w-{key}</span> <div className="flex-none w-0.5 h-1 bg-slate-300 dark:bg-white" /> </motion.div> </li> ))} </motion.ul> ) } function Sizing() { return ( <> <Bars sizes={[96, 80, 72, 64, 60, 56, 52, 48]} className="hidden sm:block lg:hidden xl:block" /> <Bars sizes={[64, 60, 56, 52, 48, 44, 40, 36]} className="sm:hidden lg:block xl:hidden" /> </> ) } function Colors() { return ( <motion.ul exit={{ opacity: 0 }} className="relative space-y-6 font-mono text-[0.625rem] leading-5 pt-5 px-5" > {['sky', 'blue', 'indigo', 'purple'].map((color, i) => ( <motion.li key={color} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: i * 0.1 }} className="bg-white rounded-lg shadow ring-1 ring-slate-700/5 p-2 dark:bg-slate-900 dark:ring-white/10" > <ul className="grid grid-cols-5 sm:grid-cols-10 lg:grid-cols-5 xl:grid-cols-10 gap-2"> {Object.keys(defaultConfig.theme.colors[color]).map((key) => ( <li key={key} className="pt-full rounded-sm ring-1 ring-inset ring-slate-900/5 dark:ring-0 dark:highlight-white/10" style={{ backgroundColor: defaultConfig.theme.colors[color][key], }} /> ))} </ul> <div className="mt-2 flex items-center justify-between text-slate-500"> <span className="flex-1">{color}-50</span> <svg width="47" height="4" viewBox="0 0 47 4" fill="currentColor"> <circle cx="1.5" cy="2" r="1.5" className="text-slate-200 dark:text-slate-800" /> <circle cx="12.5" cy="2" r="1.5" className="text-slate-300 dark:text-slate-700" /> <circle cx="23.5" cy="2" r="1.5" className="text-slate-400 dark:text-slate-600" /> <circle cx="34.5" cy="2" r="1.5" className="text-slate-300 dark:text-slate-700" /> <circle cx="45.5" cy="2" r="1.5" className="text-slate-200 dark:text-slate-800" /> </svg> <span className="flex-1 text-right">{color}-900</span> </div> </motion.li> ))} </motion.ul> ) } function Typography() { return ( <motion.div key="typography" exit={{ opacity: 0 }} className="relative h-full flex flex-col justify-center space-y-8 sm:space-y-5 lg:space-y-8 xl:space-y-5 xl:px-5" > {[ [ 'font-sans', 'text-sm leading-6 sm:text-base sm:leading-6 lg:text-sm lg:leading-6 xl:text-base xl:leading-6', ], ['font-serif', 'text-sm leading-6 sm:text-lg lg:text-sm lg:leading-6 xl:text-lg'], ['font-mono', 'text-sm leading-6 sm:leading-7 lg:leading-6 xl:leading-7'], ].map((font, i) => ( <motion.div key={font[0]} className="sm:bg-white sm:rounded-lg sm:ring-1 sm:ring-slate-700/5 sm:shadow sm:p-3 lg:bg-transparent lg:rounded-none lg:ring-0 lg:shadow-none lg:p-0 xl:bg-white xl:rounded-lg xl:ring-1 xl:ring-slate-700/5 xl:shadow xl:p-3 dark:ring-white/10 dark:sm:bg-slate-900 dark:sm:ring-1 dark:lg:bg-transparent dark:lg:ring-0 dark:xl:bg-slate-900 dark:xl:ring-1" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: i * 0.1 }} > <h4 className="text-xs leading-5 font-mono pb-2 border-b border-slate-100 text-slate-500 dark:border-slate-200/10"> {font[0]} </h4> <div className={clsx( 'mt-2 sm:mt-3 lg:mt-2 xl:mt-3 text-slate-700 dark:text-slate-400', ...font )} > The quick brown fox jumps over the lazy dog. </div> </motion.div> ))} </motion.div> ) } function Shadows() { return ( <motion.div exit={{ opacity: 0 }} className="relative h-full flex flex-col font-mono text-xs leading-5 pt-5 sm:pt-0 lg:pt-5 xl:pt-0 px-5 sm:px-8 lg:px-5 xl:px-8" > <ul className="my-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-6 -mr-0.5"> {['shadow-sm', 'shadow', 'shadow-md', 'shadow-lg', 'shadow-xl', 'shadow-2xl'].map( (shadow, i) => ( <motion.li key={shadow} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: [0.1, 0.1, 0.2, 0.2, 0.3, 0.3][i] }} className="rounded-lg" style={{ boxShadow: defaultConfig.theme.boxShadow[ shadow.replace(/^shadow-/, '').replace('shadow', 'DEFAULT') ], }} > <div className="bg-white rounded-lg p-3 pt-10 dark:bg-slate-700 dark:highlight-white/10"> {shadow} </div> </motion.li> ) )} </ul> </motion.div> ) } export function ConstraintBased() { const [tab, setTab] = useState('Sizing') return ( <section id="constraint-based" className="relative"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-indigo-500 dark:highlight-white/10" light={require('@/img/icons/home/constraint-based.png').default.src} dark={require('@/img/icons/home/dark/constraint-based.png').default.src} /> <Caption className="text-indigo-500 dark:text-indigo-400">Constraint-based</Caption> <BigText> <Widont>An API for your design system.</Widont> </BigText> <Paragraph> Utility classes help you work within the constraints of a system instead of littering your stylesheets with arbitrary values. They make it easy to be consistent with color choices, spacing, typography, shadows, and everything else that makes up a well-engineered design system. </Paragraph> <Link href="/docs/utility-first" color="indigo" darkColor="gray"> Learn more<span className="sr-only">, utility-first fundamentals</span> </Link> <div className="mt-10"> <Tabs tabs={tabs} selected={tab} onChange={(tab) => setTab(tab)} className="text-indigo-600 dark:text-indigo-400" iconClassName="text-indigo-500 dark:text-indigo-400" /> </div> </div> <GridLockup className="mt-10 xl:mt-2" left={ <div className="relative z-10 bg-white ring-1 ring-slate-900/5 rounded-lg shadow-xl px-6 py-5 my-auto xl:mt-18 dark:bg-slate-800"> <div className="absolute inset-x-0 inset-y-5 border-t border-b border-slate-100 pointer-events-none dark:border-slate-700" /> <div className="absolute inset-x-6 inset-y-0 border-l border-r border-slate-100 pointer-events-none dark:border-slate-700" /> <div className="bg-slate-50 flex overflow-hidden h-[22rem] dark:bg-slate-900/50"> <div className="relative bg-white/40 w-64 sm:w-[28rem] lg:w-64 xl:w-[28rem] mx-auto border-r border-slate-100 dark:bg-transparent dark:border-slate-100/5"> <div className="absolute inset-0 dark:hidden" style={{ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 6'%3E%3Crect x='32' width='1' height='1' fill='%23cbd5e1'/%3E%3Crect width='1' height='6' fill='%23f1f5f9'/%3E%3C/svg%3E")`, backgroundSize: '4rem 0.375rem', }} /> <div className="hidden absolute inset-0 opacity-5 dark:block" style={{ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 6'%3E%3Crect x='32' width='1' height='1' fill='%23f1f5f9'/%3E%3Crect width='1' height='6' fill='%23f1f5f9'/%3E%3C/svg%3E")`, backgroundSize: '4rem 0.375rem', }} /> <AnimatePresence initial={false} exitBeforeEnter> {tab === 'Sizing' && <Sizing key="sizing" />} {tab === 'Colors' && <Colors key="colors" />} {tab === 'Typography' && <Typography key="typography" />} {tab === 'Shadows' && <Shadows key="shadows" />} </AnimatePresence> </div> </div> </div> } right={ <CodeWindow> <AnimatePresence initial={false} exitBeforeEnter> <motion.div key={tab} className="w-full flex-auto flex min-h-0" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > <CodeWindow.Code2 lines={tokens[tab].length}> {tokens[tab].map((tokens, lineIndex) => ( <div key={lineIndex}> {tokens.map((token, tokenIndex) => { if (token.types[token.types.length - 1] === 'attr-value') { return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content.split(/\[([^\]]+)\]/).map((part, i) => i % 2 === 0 ? ( part ) : ( <span key={i} className="code-highlight bg-code-highlight"> {part} </span> ) )} </span> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} </div> ))} </CodeWindow.Code2> </motion.div> </AnimatePresence> </CodeWindow> } /> </section> ) }
BuildAnything.js
import { Fragment, useEffect, useRef, useState } from 'react' import { IconContainer, Caption, BigText, Paragraph, Link, Widont, themeTabs, } from '@/components/home/common' import { Tabs } from '@/components/Tabs' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { HtmlZenGarden } from '@/components/HtmlZenGarden' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { lines } from '../../samples/build-anything.html?highlight' const code = { Simple: `<div class="flex font-sans"> <div class="flex-none w-48 relative"> <img src="/classic-utility-jacket.jpg" alt="" class="absolute inset-0 w-full h-full object-cover" loading="lazy" /> </div> <form class="flex-auto p-6"> <div class="flex flex-wrap"> <h1 class="flex-auto text-lg font-semibold text-slate-900">Classic Utility Jacket</h1> <div class="text-lg font-semibold text-slate-500">$110.00</div> <div class="w-full flex-none text-sm font-medium text-slate-700 mt-2">In stock</div> </div> <div class="flex items-baseline mt-4 mb-6 pb-6 border-b border-slate-200"> <div class="space-x-2 flex text-sm"> <label> <input class="sr-only peer" name="size" type="radio" value="xs" checked /> <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">XS</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="s" /> <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">S</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="m" /> <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">M</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="l" /> <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">L</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="xl" /> <div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">XL</div> </label> </div> </div> <div class="flex space-x-4 mb-6 text-sm font-medium"> <div class="flex-auto flex space-x-4"> <button class="h-10 px-6 font-semibold rounded-md bg-black text-white" type="submit">Buy now</button> <button class="h-10 px-6 font-semibold rounded-md border border-slate-200 text-slate-900" type="button">Add to bag</button> </div> <button class="flex-none flex items-center justify-center w-9 h-9 rounded-md text-slate-300 border border-slate-200" type="button" aria-label="Like"> <svg width="20" height="20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" /> </svg> </button> </div> <p class="text-sm text-slate-700">Free shipping on all continental US orders.</p> </form> </div>`, Playful: `<div class="flex font-sans"> <div class="flex-none w-56 relative"> <img src="/classic-utility-jacket.jpg" alt="" class="absolute inset-0 w-full h-full object-cover rounded-lg" loading="lazy" /> </div> <form class="flex-auto p-6"> <div class="flex flex-wrap"> <h1 class="flex-auto font-medium text-slate-900">Classic Utility Jacket</h1> <div class="w-full flex-none mt-2 order-1 text-3xl font-bold text-violet-600">$110.00</div> <div class="text-sm font-medium text-slate-400">In stock</div> </div> <div class="flex items-baseline mt-4 mb-6 pb-6 border-b border-slate-200"> <div class="space-x-2 flex text-sm font-bold"> <label> <input class="sr-only peer" name="size" type="radio" value="xs" checked /> <div class="w-9 h-9 rounded-full flex items-center justify-center text-violet-400 peer-checked:bg-violet-600 peer-checked:text-white">XS</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="s" /> <div class="w-9 h-9 rounded-full flex items-center justify-center text-violet-400 peer-checked:bg-violet-600 peer-checked:text-white">S</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="m" /> <div class="w-9 h-9 rounded-full flex items-center justify-center text-violet-400 peer-checked:bg-violet-600 peer-checked:text-white">M</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="l" /> <div class="w-9 h-9 rounded-full flex items-center justify-center text-violet-400 peer-checked:bg-violet-600 peer-checked:text-white">L</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="xl" /> <div class="w-9 h-9 rounded-full flex items-center justify-center text-violet-400 peer-checked:bg-violet-600 peer-checked:text-white">XL</div> </label> </div> </div> <div class="flex space-x-4 mb-5 text-sm font-medium"> <div class="flex-auto flex space-x-4"> <button class="h-10 px-6 font-semibold rounded-full bg-violet-600 text-white" type="submit">Buy now</button> <button class="h-10 px-6 font-semibold rounded-full border border-slate-200 text-slate-900" type="button">Add to bag</button> </div> <button class="flex-none flex items-center justify-center w-9 h-9 rounded-full text-violet-600 bg-violet-50" type="button" aria-label="Like"> <svg width="20" height="20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" /> </svg> </button> </div> <p class="text-sm text-slate-500">Free shipping on all continental US orders.</p> </form> </div>`, Elegant: `<div class="flex font-serif"> <div class="flex-none w-52 relative"> <img src="/classic-utility-jacket.jpg" alt="" class="absolute inset-0 w-full h-full object-cover rounded-lg" loading="lazy" /> </div> <form class="flex-auto p-6"> <div class="flex flex-wrap items-baseline"> <h1 class="w-full flex-none mb-3 text-2xl leading-none text-slate-900">Classic Utility Jacket</h1> <div class="flex-auto text-lg font-medium text-slate-500">$350.00</div> <div class="text-xs leading-6 font-medium uppercase text-slate-500">In stock</div> </div> <div class="flex items-baseline mt-4 mb-6 pb-6 border-b border-slate-200"> <div class="space-x-1 flex text-sm font-medium"> <label> <input class="sr-only peer" name="size" type="radio" value="xs" checked /> <div class="w-7 h-7 rounded-full flex items-center justify-center text-slate-500 peer-checked:bg-slate-100 peer-checked:text-slate-900">XS</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="s" /> <div class="w-7 h-7 rounded-full flex items-center justify-center text-slate-500 peer-checked:bg-slate-100 peer-checked:text-slate-900">S</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="m" /> <div class="w-7 h-7 rounded-full flex items-center justify-center text-slate-500 peer-checked:bg-slate-100 peer-checked:text-slate-900">M</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="l" /> <div class="w-7 h-7 rounded-full flex items-center justify-center text-slate-500 peer-checked:bg-slate-100 peer-checked:text-slate-900">L</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="xl" /> <div class="w-7 h-7 rounded-full flex items-center justify-center text-slate-500 peer-checked:bg-slate-100 peer-checked:text-slate-900">XL</div> </label> </div> </div> <div class="flex space-x-4 mb-5 text-sm font-medium"> <div class="flex-auto flex space-x-4 pr-4"> <button class="flex-none w-1/2 h-12 uppercase font-medium tracking-wider bg-slate-900 text-white" type="submit">Buy now</button> <button class="flex-none w-1/2 h-12 uppercase font-medium tracking-wider border border-slate-200 text-slate-900" type="button">Add to bag</button> </div> <button class="flex-none flex items-center justify-center w-12 h-12 text-slate-300 border border-slate-200" type="button" aria-label="Like"> <svg width="20" height="20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" /> </svg> </button> </div> <p class="text-sm text-slate-500">Free shipping on all continental US orders.</p> </form> </div>`, Brutalist: `<div class="flex p-6 font-mono"> <div class="flex-none w-48 mb-10 relative z-10 before:absolute before:top-1 before:left-1 before:w-full before:h-full before:bg-teal-400"> <img src="/classic-utility-jacket.jpg" alt="" class="absolute z-10 inset-0 w-full h-full object-cover rounded-lg" loading="lazy" /> </div> <form class="flex-auto pl-6"> <div class="relative flex flex-wrap items-baseline pb-6 before:bg-black before:absolute before:-top-6 before:bottom-0 before:-left-60 before:-right-6"> <h1 class="relative w-full flex-none mb-2 text-2xl font-semibold text-white">Retro Shoe</h1> <div class="relative text-lg text-white">$89.00</div> <div class="relative uppercase text-teal-400 ml-3">In stock</div> </div> <div class="flex items-baseline my-6"> <div class="space-x-3 flex text-sm font-medium"> <label> <input class="sr-only peer" name="size" type="radio" value="xs" checked /> <div class="relative w-10 h-10 flex items-center justify-center text-black peer-checked:bg-black peer-checked:text-white before:absolute before:z-[-1] before:top-0.5 before:left-0.5 before:w-full before:h-full peer-checked:before:bg-teal-400">XS</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="s" /> <div class="relative w-10 h-10 flex items-center justify-center text-black peer-checked:bg-black peer-checked:text-white before:absolute before:z-[-1] before:top-0.5 before:left-0.5 before:w-full before:h-full peer-checked:before:bg-teal-400">S</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="m" /> <div class="relative w-10 h-10 flex items-center justify-center text-black peer-checked:bg-black peer-checked:text-white before:absolute before:z-[-1] before:top-0.5 before:left-0.5 before:w-full before:h-full peer-checked:before:bg-teal-400">M</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="l" /> <div class="relative w-10 h-10 flex items-center justify-center text-black peer-checked:bg-black peer-checked:text-white before:absolute before:z-[-1] before:top-0.5 before:left-0.5 before:w-full before:h-full peer-checked:before:bg-teal-400">L</div> </label> <label> <input class="sr-only peer" name="size" type="radio" value="xl" /> <div class="relative w-10 h-10 flex items-center justify-center text-black peer-checked:bg-black peer-checked:text-white before:absolute before:z-[-1] before:top-0.5 before:left-0.5 before:w-full before:h-full peer-checked:before:bg-teal-400">XL</div> </label> </div> </div> <div class="flex space-x-2 mb-4 text-sm font-medium"> <div class="flex space-x-4"> <button class="px-6 h-12 uppercase font-semibold tracking-wider border-2 border-black bg-teal-400 text-black" type="submit">Buy now</button> <button class="px-6 h-12 uppercase font-semibold tracking-wider border border-slate-200 text-slate-900" type="button">Add to bag</button> </div> <button class="flex-none flex items-center justify-center w-12 h-12 text-black" type="button" aria-label="Like"> <svg width="20" height="20" fill="currentColor" aria-hidden="true"> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" /> </svg> </button> </div> <p class="text-xs leading-6 text-slate-500">Free shipping on all continental US orders.</p> </form> </div>`, } function extractClasses(code) { return code.match(/class="[^"]+"/g).map((attr) => attr.substring(7, attr.length - 1)) } const classes = { Simple: extractClasses(code.Simple), Playful: extractClasses(code.Playful), Elegant: extractClasses(code.Elegant), Brutalist: extractClasses(code.Brutalist), } const content = { Simple: ['/classic-utility-jacket.jpg', 'Classic Utility Jacket', '$110.00'], Playful: ['/kids-jumpsuit.jpg', 'Kids Jumpsuit', '$39.00'], Elegant: ['/dogtooth-style-jacket.jpg', 'DogTooth Style Jacket', '$350.00'], Brutalist: ['/retro-shoe.jpg', 'Retro Shoe', '$89.00'], } export function BuildAnything() { const [theme, setTheme] = useState('Simple') let classIndex = 0 let contentIndex = 0 const initial = useRef(true) useEffect(() => { initial.current = false }, []) return ( <section id="build-anything"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-pink-500 dark:highlight-white/30" light={require('@/img/icons/home/build-anything.png').default.src} dark={require('@/img/icons/home/dark/build-anything.png').default.src} /> <Caption className="text-pink-500 dark:text-pink-400">Build anything</Caption> <BigText> <Widont>Build whatever you want, seriously.</Widont> </BigText> <Paragraph> Because Tailwind is so low-level, it never encourages you to design the same site twice. Even with the same color palette and sizing scale, it's easy to build the same component with a completely different look in the next project. </Paragraph> <Link href="/docs/installation" color="pink" darkColor="gray"> Get started<span className="sr-only">, installation</span> </Link> <div className="mt-10"> <Tabs className="text-pink-500 dark:text-pink-400" iconClassName="text-pink-500 dark:text-pink-400" tabs={themeTabs} selected={theme} onChange={setTheme} /> </div> </div> <GridLockup className="mt-10 xl:mt-2" beams={1} left={<HtmlZenGarden theme={theme} />} right={ <CodeWindow> <CodeWindow.Code2 lines={lines.length}> {lines.map((tokens, lineIndex) => ( <Fragment key={lineIndex}> {tokens.map((token, tokenIndex) => { if (token.content === '_') { let cls = classes[theme][classIndex++] return ( <span key={cls} className={clsx('code-highlight', getClassNameForToken(token), { 'animate-flash-code': !initial.current, })} > {cls} </span> ) } if (token.content.includes('__content__')) { let text = content[theme][contentIndex++] return ( <Fragment key={text}> {token.content.split(/(__content__)/).map((part, i) => i === 1 ? ( <span key={i} className={clsx('code-highlight', getClassNameForToken(token), { 'animate-flash-code': !initial.current, })} > {text} </span> ) : ( part ) )} </Fragment> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} {'\n'} </Fragment> ))} </CodeWindow.Code2> </CodeWindow> } /> </section> ) }
Performance.js
import { IconContainer, Caption, BigText, Paragraph, Link } from '@/components/home/common' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { TabBar } from '@/components/TabBar' import { Fragment, useCallback, useEffect, useRef, useState } from 'react' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { lines as html } from '../../samples/performance.html?highlight' import { lines as css } from '../../samples/performance.txt?highlight=css' import { useInView } from 'react-intersection-observer' import { animate } from 'framer-motion' const START_DELAY = 500 const CLASS_DELAY = 1000 const CHAR_DELAY = 75 const SAVE_DELAY = 50 const BUILD_DELAY = 100 function Typing({ classes, rules, onStartedClass, onFinishedClass }) { let [text, setText] = useState('') useEffect(() => { let newText = classes.substr(0, text.length + 1) let isSpace = newText.endsWith(' ') let isEnd = text.length + 1 > classes.length let isEndOfClass = isSpace || isEnd if (isEndOfClass) { onFinishedClass(newText.split(' ').filter(Boolean).length - 1) } let handle = window.setTimeout( () => { if (newText.endsWith(' ') || newText.length === 1) { onStartedClass() } setText(newText) }, isSpace ? CLASS_DELAY : CHAR_DELAY ) return () => { window.clearTimeout(handle) } }, [classes, text, onStartedClass, onFinishedClass]) return text.split(' ').map((cls, index) => ( <Fragment key={cls}> {index !== 0 && ' '} <span className={clsx( 'whitespace-nowrap', index === rules.length - 1 && 'code-highlight animate-flash-code-slow' )} > {cls} </span> </Fragment> )) } export function Performance() { let [visibleRules, setVisibleRules] = useState([]) let [saved, setSaved] = useState(true) let [lastFinishedClass, setLastFinishedClass] = useState(-1) let [active, setActive] = useState(false) let scrollRef = useRef() let { ref: typingRef, inView: typingInView } = useInView({ threshold: 1 }) let { ref: containerRef, inView: containerInView } = useInView({ threshold: 0 }) useEffect(() => { if (typingInView && !active) { let handle = window.setTimeout(() => setActive(true), START_DELAY) return () => { window.clearTimeout(handle) } } }, [active, typingInView]) useEffect(() => { if (!containerInView && active) { setActive(false) setVisibleRules([]) setSaved(true) setLastFinishedClass(-1) } }, [active, containerInView]) let rules = [] let chunk = [] for (let line of css) { chunk.push(line) let empty = line.every(({ content }) => content.trim() === '') if (empty) { rules.push(chunk) chunk = [] } } rules = rules.filter((_, i) => visibleRules.includes(i)) let onStartedClass = useCallback(() => { setSaved(false) }, []) useEffect(() => { if (lastFinishedClass < 0) return let handle1 = window.setTimeout(() => setSaved(true), SAVE_DELAY) let handle2 = window.setTimeout( () => setVisibleRules( [ [0], [0, 1], [0, 1, 3], [0, 1, 3, 4], [0, 1, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6], ][lastFinishedClass] ), SAVE_DELAY + BUILD_DELAY ) return () => { window.clearTimeout(handle1) window.clearTimeout(handle2) } }, [lastFinishedClass]) return ( <section id="performance"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-sky-500 dark:highlight-white/20" light={require('@/img/icons/home/performance.png').default.src} dark={require('@/img/icons/home/dark/performance.png').default.src} /> <Caption className="text-sky-500">Performance</Caption> <BigText>It’s tiny — never ship unused CSS again.</BigText> <Paragraph> Tailwind automatically removes all unused CSS when building for production, which means your final CSS bundle is the smallest it could possibly be. In fact, most Tailwind projects ship less than 10kB of CSS to the client. </Paragraph> <Link href="/docs/optimizing-for-production" color="sky" darkColor="gray"> Learn more<span className="sr-only">, optimizing for production</span> </Link> </div> <GridLockup className="mt-14" overhang="md" left={ <div className="relative"> <div ref={containerRef} className="relative bg-slate-800 shadow-xl pt-2 overflow-hidden sm:rounded-xl lg:grid lg:grid-cols-2 lg:grid-rows-1 dark:bg-slate-900/70 dark:backdrop-blur dark:ring-1 dark:ring-inset dark:ring-white/10" > <div className="row-end-1"> <TabBar side="left" translucent={true} primary={{ name: 'index.html', saved }} secondary={[ { name: 'tailwind.config.js' }, { name: 'package.json', open: false, className: 'hidden sm:block' }, ]} > <svg width="15" height="14" fill="none" stroke="currentColor"> <rect width="14" height="13" x="0.5" y="0.5" rx="3" /> <path d="M7.5 0V14" /> </svg> <svg width="12" height="2" fill="currentColor"> <circle cx="1" cy="1" r="1" /> <circle cx="6" cy="1" r="1" /> <circle cx="11" cy="1" r="1" /> </svg> </TabBar> <CodeWindow.Code2 lines={html.length} language="html" wrap={true} showLineNumbers={false} overflow={false} className="border-r border-slate-500/30 h-[20.8125rem] overflow-hidden p-4 md:pl-0" > {html.map((tokens, lineIndex) => ( <div key={lineIndex} className="flex"> <div className="hidden md:block text-slate-600 flex-none pr-4 text-right select-none w-[3.125rem] mr-4"> {lineIndex + 1} </div> <div> {tokens.map((token, tokenIndex) => { if (token.content === '__CLASSES__') { return ( <span key={tokenIndex} ref={typingRef} className={getClassNameForToken(token)} > {active && ( <Typing classes="flex items-center px-4 py-3 text-white bg-blue-500 hover:bg-blue-400" rules={rules} onStartedClass={onStartedClass} onFinishedClass={setLastFinishedClass} /> )} <span className="border -mx-px" style={{ height: '1.125rem' }} /> </span> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} </div> </div> ))} </CodeWindow.Code2> </div> <div className="row-span-2 border-t border-slate-500/30 pt-1.5 lg:border-0 lg:pt-0"> <TabBar side="right" translucent={true} primary={{ name: 'build.css', saved: true }} > <svg width="12" height="2" fill="currentColor"> <circle cx="1" cy="1" r="1" /> <circle cx="6" cy="1" r="1" /> <circle cx="11" cy="1" r="1" /> </svg> </TabBar> <CodeWindow.Code2 ref={scrollRef} language="css" overflow={false} lines={Math.max(1, rules.flat().length)} className="h-[20.8125rem] lg:h-[31.6875rem] scroll-smooth overflow-hidden" > {rules.map((rule) => ( <Rule key={rule[0].find(({ content }) => content.trim()).content} rule={rule} scrollRef={scrollRef} /> ))} </CodeWindow.Code2> </div> <div className="row-start-1 row-end-2 border-t border-slate-500/30"> <div className="h-1.5 border-r border-slate-500/30" /> <TabBar side="right" translucent={true} primary={{ name: 'Terminal' }} showTabMarkers={false} > <svg width="12" height="2" fill="currentColor"> <circle cx="1" cy="1" r="1" /> <circle cx="6" cy="1" r="1" /> <circle cx="11" cy="1" r="1" /> </svg> </TabBar> <Terminal rules={rules} /> </div> </div> </div> } /> </section> ) } function Terminal({ rules }) { let scrollRef = useRef() useEffect(() => { let top = scrollRef.current.scrollHeight - scrollRef.current.offsetHeight if (CSS.supports('scroll-behavior', 'smooth')) { scrollRef.current.scrollTo({ top }) } else { animate(scrollRef.current.scrollTop, top, { onUpdate: (top) => scrollRef.current.scrollTo({ top }), }) } }, [rules.length]) return ( <div ref={scrollRef} className="flex-auto border-r border-slate-500/30 text-slate-400 font-mono p-4 pb-0 h-[8.75rem] overflow-hidden scroll-smooth flex" > <svg viewBox="0 -9 3 24" className="flex-none overflow-visible text-pink-400 w-auto h-6 mr-3"> <path d="M0 0L3 3L0 6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> <pre className="flex-auto leading-6 text-sm"> <code className="block space-y-1 pb-4"> <div> npx tailwindcss <span className="xl:hidden">-o</span> <span className="hidden xl:inline">--output</span> build.css --content index.html{' '} <span className="xl:hidden">-w</span> <span className="hidden xl:inline">--watch</span> </div> {rules.map((_rule, index) => ( <div key={index} className={index === rules.length - 1 ? 'text-slate-200' : undefined}> <span className="code-highlight animate-flash-code-slow"> Rebuilding... Done in {[5, 6, 5, 7, 4, 5][index % 6]}ms. </span> </div> ))} </code> </pre> </div> ) } function Rule({ rule, scrollRef }) { let ref = useRef() useEffect(() => { let top = ref.current.offsetTop - scrollRef.current.offsetHeight / 2 + ref.current.offsetHeight / 2 if (CSS.supports('scroll-behavior', 'smooth')) { scrollRef.current.scrollTo({ top }) } else { animate(scrollRef.current.scrollTop, top, { onUpdate: (top) => scrollRef.current.scrollTo({ top }), }) } }, [scrollRef]) return ( <div ref={ref}> {rule.map((tokens, lineIndex) => { let contentIndex = tokens.findIndex(({ content }) => content.trim()) if (contentIndex === -1) { return '\n' } return ( <Fragment key={lineIndex}> {tokens.slice(0, contentIndex).map((token) => token.content)} <span className="code-highlight animate-flash-code-slow"> {tokens.slice(contentIndex).map((token, tokenIndex) => { return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} </span> {'\n'} </Fragment> ) })} </div> ) }
MobileFirst.js
import { IconContainer, Caption, BigText, Paragraph, Link } from '@/components/home/common' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { motion, useTransform, useMotionValue } from 'framer-motion' import { useEffect, useRef, useState } from 'react' import { addClassTokens2 } from '@/utils/addClassTokens' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { useIsomorphicLayoutEffect } from '@/hooks/useIsomorphicLayoutEffect' import { lines } from '../../pages/examples/mobile-first-demo' addClassTokens2(lines) const MIN_WIDTH = 400 function BrowserWindow({ size, onChange }) { let x = useMotionValue(0) let constraintsRef = useRef() let handleRef = useRef() let iframeRef = useRef() let iframePointerEvents = useMotionValue('auto') useEffect(() => { function onMessage(e) { if (e.source === iframeRef.current.contentWindow) { onChange(e.data) } } window.addEventListener('message', onMessage) return () => { window.removeEventListener('message', onMessage) } }, []) useIsomorphicLayoutEffect(() => { let observer = new window.ResizeObserver(() => { let width = constraintsRef.current.offsetWidth - handleRef.current.offsetWidth if (x.get() > width) { x.set(width) } }) observer.observe(constraintsRef.current) return () => { observer.disconnect() } }, []) useEffect(() => { handleRef.current.onselectstart = () => false }, []) return ( <div className="relative"> <motion.div className="shadow-xl sm:rounded-xl min-w-full max-w-full demo-sm:min-w-0 demo-sm:max-w-none" style={{ width: useTransform(x, (x) => x + MIN_WIDTH) }} > <div className="sm:rounded-xl ring-1 ring-slate-900/5"> <div className="sm:rounded-t-xl bg-gradient-to-b from-white to-[#FBFBFB] dark:bg-none dark:bg-slate-700 dark:highlight-white/10"> <div className={clsx( 'py-2.5 grid items-center px-4', size === undefined ? 'gap-6' : 'gap-8' )} style={{ gridTemplateColumns: size === undefined ? '2.625rem 1fr 2.625rem' : '7.125rem 1fr 7.125rem', }} > <div className="flex items-center"> <div className="w-2.5 h-2.5 rounded-full bg-[#EC6A5F]" /> <div className="ml-1.5 w-2.5 h-2.5 rounded-full bg-[#F4BF50]" /> <div className="ml-1.5 w-2.5 h-2.5 rounded-full bg-[#61C454]" /> {size !== undefined && ( <> <svg width="24" height="24" fill="none" className="ml-4 flex-none text-slate-400 dark:text-slate-500" > <path d="m15 7-5 5 5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> <svg width="24" height="24" fill="none" className="ml-2 flex-none text-slate-400 dark:text-slate-500" > <path d="m10 7 5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> </> )} </div> <div> <div className="bg-slate-100 rounded-md font-medium text-xs leading-6 py-1 flex items-center justify-center ring-1 ring-inset ring-slate-900/5 mx-auto w-4/5 dark:bg-slate-800 dark:text-slate-500"> <svg viewBox="0 0 20 20" fill="currentColor" className="text-slate-300 w-3.5 h-3.5 mr-1.5 dark:text-slate-500" > <path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" /> </svg> workcation.com </div> </div> {size !== undefined && ( <div className="flex justify-end"> <svg width="24" height="24" fill="none" className="text-slate-400 dark:text-slate-500" > <path d="M12.5 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM12.5 12a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM18.5 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM18.5 12a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM6.5 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM6.5 12a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM12.5 18a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM18.5 18a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0ZM6.5 18a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" /> </svg> </div> )} </div> <div className="grid grid-cols-3 text-xs leading-5 overflow-hidden"> <div className="pointer-events-none select-none bg-slate-100 text-slate-400 rounded-tr border border-slate-900/5 px-4 py-1.5 -mb-px -ml-px flex items-center justify-center space-x-2 dark:bg-slate-800 dark:text-slate-500"> <svg width="17" height="10" fill="currentColor" className="flex-none text-slate-300 dark:text-slate-500" > <path fillRule="evenodd" clipRule="evenodd" d="M8.5 0C6.233 0 4.817 1.111 4.25 3.334c.85-1.112 1.842-1.528 2.975-1.25.647.158 1.109.618 1.62 1.127C9.68 4.041 10.643 5 12.75 5c2.267 0 3.683-1.111 4.25-3.333-.85 1.111-1.841 1.528-2.975 1.25-.647-.159-1.109-.619-1.62-1.128C11.57.96 10.607 0 8.5 0ZM4.25 5C1.983 5 .567 6.111 0 8.334c.85-1.112 1.842-1.528 2.975-1.25.647.158 1.109.618 1.62 1.127C5.43 9.041 6.393 10 8.5 10c2.267 0 3.684-1.11 4.25-3.333-.85 1.111-1.842 1.528-2.975 1.25-.647-.159-1.109-.619-1.62-1.128C7.32 5.96 6.357 5 4.25 5Z" /> </svg> <div className="truncate">Tailwind UI - Official Tailwind CSS Components</div> </div> <div className="pointer-events-none select-none text-slate-900 font-medium px-4 py-1.5 flex items-center justify-center space-x-2 dark:text-slate-200"> <svg width="15" height="14" fill="currentColor" className="flex-none text-indigo-600 dark:text-slate-400" > <path d="M6.541 11.753a1.803 1.803 0 0 1-.485 1.277c-.241.253-.552.426-.89.497-.34.07-.691.034-1.01-.103a1.736 1.736 0 0 1-.776-.67 1.79 1.79 0 0 1-.272-1c.004-.306.086-.604.239-.866.152-.262.37-.48.63-.628-.01.047.039-.024 0 0l.797-.723a3.759 3.759 0 0 0 .988-2.535c0-1.28-.734-2.581-1.788-3.262.04.024-.015-.041 0 0a1.72 1.72 0 0 1-.63-.628 1.766 1.766 0 0 1-.238-.865A1.802 1.802 0 0 1 3.592.97c.24-.253.55-.426.89-.496.338-.07.69-.035 1.008.102.319.139.59.372.776.67.187.298.282.647.272 1a3.77 3.77 0 0 0 1.006 2.552l.35.35c.14.125.287.241.44.35.265.143.489.36.644.625a1.73 1.73 0 0 1-.645 2.381c.015-.03-.027.016 0 0a3.89 3.89 0 0 0-1.296 1.393 4.007 4.007 0 0 0-.496 1.856Zm1.921-9.512c0 .348.101.69.29.979.188.29.457.515.77.648a1.678 1.678 0 0 0 1.872-.382 1.803 1.803 0 0 0 .372-1.919 1.752 1.752 0 0 0-.632-.79 1.685 1.685 0 0 0-2.168.22c-.322.33-.503.778-.504 1.244Zm1.718 7.751c-.34 0-.672.104-.954.297a1.752 1.752 0 0 0-.633.79A1.802 1.802 0 0 0 8.966 13a1.679 1.679 0 0 0 1.871.382c.314-.134.582-.36.77-.65a1.796 1.796 0 0 0-.214-2.223 1.684 1.684 0 0 0-1.214-.516Zm4.393-2.995c0-.348-.1-.688-.29-.978a1.727 1.727 0 0 0-.77-.649 1.677 1.677 0 0 0-.993-.1 1.7 1.7 0 0 0-.878.482 1.803 1.803 0 0 0-.373 1.92c.13.32.35.596.633.79a1.684 1.684 0 0 0 2.167-.22c.323-.331.504-.779.504-1.245Z" /> <path d="M2.147 5.237c-.34 0-.672.103-.954.296a1.753 1.753 0 0 0-.633.79 1.803 1.803 0 0 0 .373 1.92c.24.245.545.413.878.48.333.069.679.034.993-.099.314-.133.582-.359.77-.648a1.795 1.795 0 0 0-.214-2.223 1.714 1.714 0 0 0-1.213-.516Z" /> </svg> <div className="truncate">Workcation - Find a trip that suits you</div> </div> <div className="pointer-events-none select-none bg-slate-100 text-slate-400 rounded-tl border border-slate-900/5 pl-4 pr-8 py-1.5 -mb-px -mr-4 flex items-center justify-center space-x-2 dark:bg-slate-800 dark:text-slate-500"> <svg width="15" height="16" fill="currentColor" className="flex-none text-slate-300 dark:text-slate-500" > <path d="m2.973 9.822 9.154-3.056c-.183-1.144-.314-1.908-.465-2.491-.162-.627-.291-.795-.342-.853a1.785 1.785 0 0 0-.643-.467c-.071-.03-.27-.102-.917-.063-.684.042-1.581.181-3.003.406-1.42.225-2.318.37-2.98.542-.627.162-.796.292-.854.342a1.792 1.792 0 0 0-.466.643c-.03.071-.102.271-.063.918.041.683.181 1.581.406 3.002.063.399.12.755.173 1.077Z" /> <path fillRule="evenodd" clipRule="evenodd" d="M.447 9.117C.012 6.367-.206 4.993.265 3.89a4.166 4.166 0 0 1 1.09-1.5C2.26 1.6 3.633 1.382 6.382.946c2.75-.436 4.125-.653 5.229-.182a4.164 4.164 0 0 1 1.5 1.09c.79.904 1.007 2.278 1.442 5.028.436 2.75.653 4.124.182 5.227a4.164 4.164 0 0 1-1.09 1.5c-.903.79-2.278 1.008-5.028 1.443-2.749.436-4.124.653-5.227.182a4.166 4.166 0 0 1-1.5-1.09C1.1 13.241.883 11.867.447 9.117Zm4.85 4.882c.735-.044 1.684-.193 3.087-.415 1.404-.222 2.351-.374 3.066-.56.691-.179 1.01-.354 1.216-.534a2.68 2.68 0 0 0 .7-.964c.108-.252.176-.609.133-1.322-.045-.736-.193-1.685-.416-3.088-.222-1.404-.373-2.352-.559-3.066-.18-.692-.354-1.01-.534-1.216a2.678 2.678 0 0 0-.964-.7c-.252-.108-.609-.176-1.323-.133-.736.044-1.684.193-3.088.415-1.403.223-2.35.374-3.065.56-.692.179-1.01.354-1.216.534a2.678 2.678 0 0 0-.7.964c-.108.251-.176.609-.133 1.322.045.737.193 1.685.415 3.088.223 1.404.374 2.352.56 3.066.179.692.354 1.01.534 1.216.265.303.594.543.964.7.252.109.608.176 1.323.133Z" /> </svg> <div className="truncate"> Headless UI – Unstyled, fully accessible UI components </div> </div> </div> </div> <div className="relative bg-white border-t border-slate-200 rounded-b-xl pb-8 -mb-8 dark:bg-slate-800 dark:border-slate-900/50"> <motion.iframe ref={iframeRef} src="/examples/mobile-first-demo" title="Mobile-first Demo" className="w-full h-[30.625rem]" style={{ pointerEvents: iframePointerEvents }} /> </div> </div> </motion.div> <div ref={constraintsRef} className="absolute inset-y-0 pointer-events-none" style={{ right: `-${22 / 16}rem`, width: `calc(100% - ${MIN_WIDTH}px + ${22 / 16}rem)`, }} > <motion.div ref={handleRef} drag="x" _dragX={x} dragMomentum={false} dragElastic={0} dragConstraints={constraintsRef} className="absolute z-10 top-1/2 left-0 p-2 -mt-6 hidden demo-sm:flex items-center justify-center pointer-events-auto cursor-ew-resize" style={{ x }} onDragStart={() => { document.documentElement.classList.add('dragging-ew') iframePointerEvents.set('none') }} onDragEnd={() => { document.documentElement.classList.remove('dragging-ew') iframePointerEvents.set('auto') }} > <div className="w-1.5 h-8 bg-slate-500/60 rounded-full" /> </motion.div> </div> </div> ) } function Marker({ label, active, className }) { return ( <div className={clsx('flex items-start flex-none', className)}> <div className="flex flex-col items-center ml-[-2px]"> <div className={clsx('w-px h-14', active ? 'bg-indigo-600' : 'bg-slate-100 dark:bg-slate-800')} /> <div className={clsx( 'mt-[3px] w-[5px] h-[5px] shadow-sm rounded-full ring-1', active ? 'bg-indigo-600 ring-indigo-600' : 'bg-white ring-slate-500/[0.15] dark:bg-slate-900 dark:ring-slate-700' )} /> </div> <div className={clsx( 'ml-1.5 rounded font-mono text-[0.625rem] leading-6 px-1.5 ring-1 ring-inset dark:ring-0', active ? 'bg-indigo-50 text-indigo-600 ring-indigo-600 dark:bg-indigo-500 dark:text-white dark:highlight-white/10' : 'bg-slate-100 ring-slate-100 dark:bg-slate-800 dark:highlight-white/5' )} > {label} </div> </div> ) } export function MobileFirst() { let [size, setSize] = useState() return ( <section id="mobile-first" className="overflow-hidden"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-indigo-500 dark:highlight-white/20" light={require('@/img/icons/home/mobile-first.png').default.src} dark={require('@/img/icons/home/dark/mobile-first.png').default.src} /> <Caption className="text-indigo-500 dark:text-indigo-400">Mobile-first</Caption> <BigText>Responsive everything.</BigText> <Paragraph as="div"> <p> Wrestling with a bunch of complex media queries in your CSS sucks, so Tailwind lets you build responsive designs right in your HTML instead. </p> <p> Throw a screen size in front of literally any utility class and watch it magically apply at a specific breakpoint. </p> </Paragraph> <Link href="/docs/responsive-design" color="indigo" darkColor="gray"> Learn more<span className="sr-only">, responsive design</span> </Link> </div> <div className="hidden mt-16 mb-12 border-b border-slate-100 xl:mb-0 demo-sm:block dark:border-slate-800"> <div className="mb-[-3px] flex max-w-7xl mx-auto px-6 sm:px-8 md:px-10"> <Marker label="sm" active={size !== undefined} className="ml-[40rem] w-32" /> <Marker label="md" active={size === 'md' || size === 'lg'} className="w-64" /> <Marker label="lg" active={size === 'lg'} className="w-64" /> <Marker label="xl" className="w-64" /> <Marker label="2xl" /> </div> </div> <GridLockup className="mt-10 demo-sm:-mt-2.5" overhang="md" beams={0} left={ <> <div className="sm:px-2 demo-sm:-mt-24 xl:mt-0"> <BrowserWindow size={size} onChange={setSize} /> </div> <CodeWindow className="!max-h-[24.75rem] lg:!h-[24.75rem]"> <CodeWindow.Code2 lines={lines.length}> {lines.map((tokens, lineIndex) => ( <div key={lineIndex}> {tokens.map((token, tokenIndex) => { if (token.types[token.types.length - 1] === 'class') { let isSm = token.content.startsWith('sm:') let isMd = token.content.startsWith('md:') let isLg = token.content.startsWith('lg:') if (isSm || isMd || isLg) { let faded = size === undefined || (size === 'sm' && (isMd || isLg)) || (size === 'md' && isLg) let highlighted = (size === 'sm' && isSm) || (size === 'md' && isMd) || (size === 'lg' && isLg) return ( <span key={tokenIndex} className={clsx( 'code-highlight transition duration-500', getClassNameForToken(token), { 'opacity-50': faded, 'bg-code-highlight': highlighted } )} > {token.content} </span> ) } } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} </div> ))} </CodeWindow.Code2> </CodeWindow> </> } /> </section> ) }
StateVariants.js
import { IconContainer, Caption, BigText, Paragraph, Link, Widont, InlineCode, } from '@/components/home/common' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { addClassTokens2 } from '@/utils/addClassTokens' import { useEffect, useRef, useState } from 'react' import { usePrevious } from '@/hooks/usePrevious' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { lines } from '../../samples/state-variants.html?highlight' import { animate } from 'framer-motion' const projects = [ { title: 'API Integration', category: 'Engineering' }, { title: 'New Benefits Plan', category: 'Human Resources' }, { title: 'Onboarding Emails', category: 'Customer Success' }, ] const faces = [ 'photo-1531123897727-8f129e1688ce', 'photo-1494790108377-be9c29b29330', 'photo-1552374196-c4e7ffc6e126', 'photo-1546525848-3ce03ca516f6', 'photo-1544005313-94ddf0286df2', 'photo-1517841905240-472988babdf9', 'photo-1506794778202-cad84cf45f1d', 'photo-1500648767791-00dcc994a43e', 'photo-1534528741775-53994a69daeb', 'photo-1502685104226-ee32379fefbe', 'photo-1546525848-3ce03ca516f6', 'photo-1502685104226-ee32379fefbe', 'photo-1494790108377-be9c29b29330', 'photo-1506794778202-cad84cf45f1d', 'photo-1534528741775-53994a69daeb', ] addClassTokens2(lines) const lineRanges = { 'new-btn-hover': [4, 9], 'input-focus': [15, 15], 'item-hover': [20, 39], 'new-hover': [42, 47], } export function StateVariants() { const [states, setStates] = useState([]) const prevStates = usePrevious(states) const codeContainerRef = useRef() const linesContainerRef = useRef() function scrollTo(rangeOrRanges) { let ranges = Array.isArray(rangeOrRanges) ? rangeOrRanges : [rangeOrRanges] if (ranges.length === 0) return let linesSorted = ranges.flat().sort((a, b) => a - b) let minLine = linesSorted[0] let maxLine = linesSorted[linesSorted.length - 1] let $lines = linesContainerRef.current.children let containerHeight = codeContainerRef.current.offsetHeight let top = $lines[minLine].offsetTop let height = $lines[maxLine].offsetTop + $lines[maxLine].offsetHeight - top top = top - containerHeight / 2 + height / 2 if (CSS.supports('scroll-behavior', 'smooth')) { codeContainerRef.current.scrollTo({ top }) } else { animate(codeContainerRef.current.scrollTop, top, { onUpdate: (top) => codeContainerRef.current.scrollTo({ top }), }) } } useEffect(() => { if (prevStates && prevStates.length > states.length) { scrollTo(states.map((state) => lineRanges[state])) } else if (states.length) { scrollTo(lineRanges[states[states.length - 1]]) } }, [states, prevStates]) return ( <section id="state-variants"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-blue-500 dark:highlight-white/20" light={require('@/img/icons/home/state-variants.png').default.src} dark={require('@/img/icons/home/dark/state-variants.png').default.src} /> <Caption className="text-blue-500">State variants</Caption> <BigText> <Widont>Hover and focus states? We got ’em.</Widont> </BigText> <Paragraph> Want to style something on hover? Stick <InlineCode>hover:</InlineCode> at the beginning of the class you want to add. Works for <InlineCode>focus</InlineCode>,{' '} <InlineCode>active</InlineCode>, <InlineCode>disabled</InlineCode>,{' '} <InlineCode>focus-within</InlineCode>, <InlineCode>focus-visible</InlineCode>, and even fancy states we invented ourselves like <InlineCode>group-hover</InlineCode>. </Paragraph> <Link href="/docs/hover-focus-and-other-states" color="blue" darkColor="gray"> Learn more<span className="sr-only">, handling hover, focus, and other states</span> </Link> </div> <GridLockup className="mt-10 xl:mt-2" beams={4} left={ <div className="relative z-10 rounded-xl bg-white shadow-xl ring-1 ring-slate-900/5 overflow-hidden my-auto xl:mt-18 dark:bg-slate-800"> <section> <header className="rounded-t-xl space-y-4 p-4 sm:px-8 sm:py-6 lg:p-4 xl:px-8 xl:py-6 dark:highlight-white/10"> <div className="flex items-center justify-between"> <h2 className="font-semibold text-slate-900 dark:text-white">Projects</h2> <div className="group flex items-center rounded-md bg-blue-500 text-white text-sm font-medium pl-2 pr-3 py-2 cursor-pointer shadow-sm hover:bg-blue-400" onMouseEnter={() => { setStates((states) => [...states, 'new-btn-hover']) }} onMouseLeave={() => { setStates((states) => states.filter((x) => x !== 'new-btn-hover')) }} > <svg width="20" height="20" fill="currentColor" className="mr-2"> <path d="M10 5a1 1 0 0 1 1 1v3h3a1 1 0 1 1 0 2h-3v3a1 1 0 1 1-2 0v-3H6a1 1 0 1 1 0-2h3V6a1 1 0 0 1 1-1Z" /> </svg> New </div> </div> <div className="group relative rounded-md dark:bg-slate-700 dark:highlight-white/10 dark:focus-within:bg-transparent"> <svg width="20" height="20" fill="currentColor" className="absolute left-3 top-1/2 -mt-2.5 text-slate-400 pointer-events-none group-focus-within:text-blue-500 dark:text-slate-500" > <path fillRule="evenodd" clipRule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" /> </svg> <input onFocus={() => { setStates((states) => [...states, 'input-focus']) }} onBlur={() => { setStates((states) => states.filter((x) => x !== 'input-focus')) // resetScroll() }} type="text" aria-label="Filter projects" placeholder="Filter projects..." className="appearance-none w-full text-sm leading-6 bg-transparent text-slate-900 placeholder:text-slate-400 rounded-md py-2 pl-10 ring-1 ring-slate-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:text-slate-100 dark:placeholder:text-slate-500 dark:ring-0 dark:focus:ring-2" /> </div> </header> <ul className="bg-slate-50 p-4 sm:px-8 sm:pt-6 sm:pb-8 lg:p-4 xl:px-8 xl:pt-6 xl:pb-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-4 text-sm leading-6 dark:bg-slate-900/40 dark:ring-1 dark:ring-white/5"> {projects.map((project, i, a) => ( <li key={i} className={clsx( 'group cursor-pointer rounded-md p-3 bg-white ring-1 ring-slate-200 shadow-sm hover:bg-blue-500 hover:ring-blue-500 hover:shadow-md dark:bg-slate-700 dark:ring-0 dark:highlight-white/10 dark:hover:bg-blue-500', i === a.length - 1 ? 'hidden sm:block lg:hidden xl:block' : '' )} onMouseEnter={() => { setStates((states) => [...states, 'item-hover']) }} onMouseLeave={() => { setStates((states) => states.filter((x) => x !== 'item-hover')) }} > <dl className="grid sm:block lg:grid xl:block grid-cols-2 grid-rows-2 items-center"> <div> <dt className="sr-only">Title</dt> <dd className="font-semibold text-slate-900 group-hover:text-white dark:text-slate-100"> {project.title} </dd> </div> <div> <dt className="sr-only">Category</dt> <dd className="group-hover:text-blue-200">{project.category}</dd> </div> <div className="col-start-2 row-start-1 row-end-3 sm:mt-4 lg:mt-0 xl:mt-4"> <dt className="sr-only">Users</dt> <dd className="flex justify-end sm:justify-start lg:justify-end xl:justify-start -space-x-1.5"> {Array.from({ length: 5 }).map((_, j) => ( <img key={j} src={`https://images.unsplash.com/${ faces[i * 5 + j] }?auto=format&fit=facearea&facepad=2&w=48&h=48&q=80`} alt="" className="w-6 h-6 rounded-full bg-slate-100 ring-2 ring-white dark:ring-slate-700 dark:group-hover:ring-white" loading="lazy" decoding="async" /> ))} </dd> </div> </dl> </li> ))} <li className="flex"> <div className="group w-full flex flex-col items-center justify-center rounded-md border-2 border-dashed border-slate-300 text-sm leading-6 text-slate-900 font-medium py-3 cursor-pointer hover:border-blue-500 hover:border-solid hover:bg-white hover:text-blue-500 dark:border-slate-700 dark:text-slate-100 dark:hover:border-blue-500 dark:hover:bg-transparent dark:hover:text-blue-500" onMouseEnter={() => { setStates((states) => [...states, 'new-hover']) }} onMouseLeave={() => { setStates((states) => states.filter((x) => x !== 'new-hover')) }} > <svg width="20" height="20" fill="currentColor" className="mb-1 text-slate-400 group-hover:text-blue-500" > <path d="M10 5a1 1 0 0 1 1 1v3h3a1 1 0 1 1 0 2h-3v3a1 1 0 1 1-2 0v-3H6a1 1 0 1 1 0-2h3V6a1 1 0 0 1 1-1Z" /> </svg> New project </div> </li> </ul> </section> </div> } right={ <CodeWindow> <CodeWindow.Code2 ref={codeContainerRef} lines={lines.length} className="scroll-smooth"> <div ref={linesContainerRef} className={clsx('mono', { 'mono-active': states.length > 0 })} > {lines.map((tokens, lineIndex) => ( <div key={lineIndex} className={ (states.includes('new-btn-hover') && lineIndex >= lineRanges['new-btn-hover'][0] && lineIndex <= lineRanges['new-btn-hover'][1]) || (states.includes('input-focus') && lineIndex >= lineRanges['input-focus'][0] && lineIndex <= lineRanges['input-focus'][1]) || (states.includes('item-hover') && lineIndex >= lineRanges['item-hover'][0] && lineIndex <= lineRanges['item-hover'][1]) || (states.includes('new-hover') && lineIndex >= lineRanges['new-hover'][0] && lineIndex <= lineRanges['new-hover'][1]) ? 'not-mono' : '' } > {tokens.map((token, tokenIndex) => { if ( token.types[token.types.length - 1] === 'class' && token.content.startsWith('(') ) { const [, state] = token.content.match(/^\(([^)]+)\)/) return ( <span key={tokenIndex} className={clsx( 'code-highlight transition-colors duration-500', getClassNameForToken(token), { 'bg-code-highlight': states.includes(state) } )} > {token.content.substr(token.content.indexOf(')') + 1)} </span> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} </div> ))} </div> </CodeWindow.Code2> </CodeWindow> } /> </section> ) }
ComponentDriven.js
import { IconContainer, Caption, BigText, Paragraph, Link, Widont, InlineCode, } from '@/components/home/common' import { GridLockup } from '@/components/GridLockup' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { Fragment, useEffect, useState } from 'react' import { useIsomorphicLayoutEffect } from '@/hooks/useIsomorphicLayoutEffect' import { Tabs } from '@/components/Tabs' import { AnimatePresence, motion } from 'framer-motion' import clsx from 'clsx' import { useInView } from 'react-intersection-observer' import { lines as reactMoviesSample } from '../../samples/react/movies.jsx?highlight' import { lines as reactNavSample } from '../../samples/react/nav.jsx?highlight' import { lines as reactNavItemSample } from '../../samples/react/nav-item.jsx?highlight' import { lines as reactListSample } from '../../samples/react/list.jsx?highlight' import { lines as reactListItemSample } from '../../samples/react/list-item.jsx?highlight' import { lines as vueMoviesSample } from '../../samples/vue/movies.html?highlight' import { lines as vueNavSample } from '../../samples/vue/nav.html?highlight' import { lines as vueNavItemSample } from '../../samples/vue/nav-item.html?highlight' import { lines as vueListSample } from '../../samples/vue/list.html?highlight' import { lines as vueListItemSample } from '../../samples/vue/list-item.html?highlight' import { lines as angularMoviesSample } from '../../samples/angular/movies.js?highlight' import { lines as angularNavSample } from '../../samples/angular/nav.js?highlight' import { lines as angularNavItemSample } from '../../samples/angular/nav-item.js?highlight' import { lines as angularListSample } from '../../samples/angular/list.js?highlight' import { lines as angularListItemSample } from '../../samples/angular/list-item.js?highlight' import { lines as bladeMoviesSample } from '../../samples/blade/movies.html?highlight' import { lines as bladeNavSample } from '../../samples/blade/nav.html?highlight' import { lines as bladeNavItemSample } from '../../samples/blade/nav-item.html?highlight' import { lines as bladeListSample } from '../../samples/blade/list.html?highlight' import { lines as bladeListItemSample } from '../../samples/blade/list-item.html?highlight' import { lines as css } from '../../samples/apply.txt?highlight=css' import { lines as html } from '../../samples/apply.html?highlight' function highlightDecorators(lines) { for (let i = 0; i < lines.length; i++) { for (let j = 0; j < lines[i].length; j++) { if (lines[i][j].types.includes('function') && lines[i][j - 1].content.trim() === '@') { lines[i][j - 1].types = ['function'] } } } return lines } const movies = [ { title: 'Prognosis Negative', starRating: '2.66', rating: 'PG-13', year: '2021', genre: 'Comedy', runtime: '1h 46m', cast: 'Simon Pegg, Zach Galifianakis', image: require('@/img/prognosis-negative.jpg').default.src, }, { title: 'Rochelle, Rochelle', starRating: '3.25', rating: 'R', year: '2020', genre: 'Romance', runtime: '1h 56m', cast: 'Emilia Clarke', image: require('@/img/rochelle-rochelle.jpg').default.src, }, { title: 'Death Blow', starRating: '4.95', rating: '18A', year: '2020', genre: 'Action', runtime: '2h 5m', cast: 'Idris Elba, John Cena, Thandiwe Newton', image: require('@/img/death-blow.jpg').default.src, }, ] const tabs = { React: { 'Movies.js': reactMoviesSample, 'Nav.js': reactNavSample, 'NavItem.js': reactNavItemSample, 'List.js': reactListSample, 'ListItem.js': reactListItemSample, }, Vue: { 'Movies.vue': vueMoviesSample, 'Nav.vue': vueNavSample, 'NavItem.vue': vueNavItemSample, 'List.vue': vueListSample, 'ListItem.vue': vueListItemSample, }, Angular: { 'movies.component.ts': highlightDecorators(angularMoviesSample), 'nav.component.ts': highlightDecorators(angularNavSample), 'nav-item.component.ts': highlightDecorators(angularNavItemSample), 'list.component.ts': highlightDecorators(angularListSample), 'list-item.component.ts': highlightDecorators(angularListItemSample), }, Blade: { 'movies.blade.php': bladeMoviesSample, 'nav.blade.php': bladeNavSample, 'nav-item.blade.php': bladeNavItemSample, 'list.blade.php': bladeListSample, 'list-item.blade.php': bladeListItemSample, }, } function ComponentLink({ onClick, children }) { const [active, setActive] = useState(false) useEffect(() => { function onKey(e) { const modifier = e.ctrlKey || e.shiftKey || e.altKey || e.metaKey if (!active && modifier) { setActive(true) } else if (active && !modifier) { setActive(false) } } window.addEventListener('keydown', onKey) window.addEventListener('keyup', onKey) return () => { window.removeEventListener('keydown', onKey) window.removeEventListener('keyup', onKey) } }, [active]) return active ? ( <button type="button" className="hover:underline" onClick={onClick}> {children} </button> ) : ( children ) } function ComponentExample({ framework }) { const [activeTab, setActiveTab] = useState(0) const lines = tabs[framework][Object.keys(tabs[framework])[activeTab]] useIsomorphicLayoutEffect(() => { setActiveTab(0) }, [framework]) return ( <CodeWindow border={false}> <AnimatePresence initial={false} exitBeforeEnter> <motion.div key={framework} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="flex-none overflow-auto whitespace-nowrap flex" > <div className="relative flex-none min-w-full px-1"> <ul className="flex text-sm leading-6 text-slate-400"> {Object.keys(tabs[framework]).map((tab, tabIndex) => ( <li key={tab} className="flex-none"> <button type="button" className={clsx( 'relative py-2 px-3', tabIndex === activeTab ? 'text-sky-300' : 'hover:text-slate-300' )} onClick={() => setActiveTab(tabIndex)} > {tab} {tabIndex === activeTab && ( <span className="absolute z-10 bottom-0 inset-x-3 h-px bg-sky-300" /> )} </button> </li> ))} </ul> <div className="absolute bottom-0 inset-x-0 h-px bg-slate-500/30" /> </div> </motion.div> </AnimatePresence> <AnimatePresence initial={false} exitBeforeEnter> <motion.div key={framework + activeTab} className="w-full flex-auto flex min-h-0" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > <CodeWindow.Code2 lines={lines.length}> {lines.map((tokens, lineIndex) => ( <Fragment key={framework + activeTab + lineIndex}> {tokens.map((token, tokenIndex) => { if ( (token.types[token.types.length - 1] === 'class-name' || (token.types[token.types.length - 1] === 'tag' && /^([A-Z]|x-)/.test(token.content))) && tokens[tokenIndex - 1]?.types[tokens[tokenIndex - 1].types.length - 1] === 'punctuation' && (tokens[tokenIndex - 1]?.content === '<' || tokens[tokenIndex - 1].content === '</') ) { return ( <span key={tokenIndex} className={getClassNameForToken(token)}> <ComponentLink onClick={() => setActiveTab( Object.keys(tabs[framework]).findIndex((x) => x.startsWith(`${token.content.replace(/^x-/, '')}.`) ) ) } > {token.content} </ComponentLink> </span> ) } if ( token.types[token.types.length - 1] === 'string' && /^(['"`])\.\/.*?\.(js|vue)\1$/.test(token.content) ) { const tab = token.content.substr(3, token.content.length - 4) return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content.substr(0, 1)} <button type="button" className="underline" onClick={() => setActiveTab(Object.keys(tabs[framework]).indexOf(tab))} > ./{tab} </button> {token.content.substr(0, 1)} </span> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} {'\n'} </Fragment> ))} </CodeWindow.Code2> </motion.div> </AnimatePresence> </CodeWindow> ) } function ApplyExample({ inView }) { return ( <CodeWindow className="!h-auto !max-h-[none]" border={false}> <h3 className="pl-4 flex text-sm leading-6 text-sky-300 border-b border-slate-500/30"> <span className="-mb-px py-2 border-b border-b-current">styles.css</span> </h3> <div className="flex-none"> <CodeWindow.Code2 lines={css.length}> {css.map((tokens, lineIndex) => ( <Fragment key={lineIndex}> {tokens.map((token, tokenIndex) => { let className = getClassNameForToken(token) if (className) { className = className .replace(/\bclass\b/, 'selector') .replace(/\b(number|color)\b/, '') } return ( <span key={tokenIndex} className={className}> {token.content} </span> ) })} {'\n'} </Fragment> ))} </CodeWindow.Code2> </div> <h3 className="pl-4 flex text-sm leading-6 text-sky-300 border-b border-slate-500/30"> <span className="-mb-px py-2 border-b border-b-current">index.html</span> </h3> <div className="overflow-hidden"> <CodeWindow.Code2 lines={html.length} initialLineNumber={31} overflow="x" className="-mt-6"> <div className={clsx('mono', { 'mono-active': inView })}> {html.map((tokens, lineIndex) => ( <div key={lineIndex} className={lineIndex >= 4 && lineIndex <= 5 ? 'not-mono' : undefined} > {tokens.map((token, tokenIndex) => { return ( <span key={tokenIndex} className={clsx(getClassNameForToken(token), 'delay-500')} style={{ transitionDuration: '1.5s' }} > {token.content} </span> ) })} </div> ))} </div> </CodeWindow.Code2> </div> </CodeWindow> ) } function AtApplySection() { let { inView, ref } = useInView({ threshold: 0.5, triggerOnce: true }) let fade = ['transition-opacity duration-[1.5s] delay-500', { 'opacity-25': inView }] return ( <div className="mt-20 relative max-w-7xl mx-auto px-4 sm:mt-32 sm:px-6 md:px-8 lg:grid lg:grid-cols-12 lg:gap-x-8 lg:grid-rows-1"> <div className="lg:col-span-7 xl:col-span-6 lg:row-end-1"> <h3 className="text-3xl text-slate-900 font-extrabold dark:text-slate-200"> Not into component frameworks? </h3> <Paragraph> Use Tailwind's <InlineCode>@apply</InlineCode> directive to extract repeated utility patterns into custom CSS classes just by copying and pasting the list of class names. </Paragraph> <Link href="/docs/reusing-styles" color="sky" darkColor="gray"> Learn more<span className="sr-only">, reusing styles</span> </Link> </div> <div className="pt-10 lg:col-span-5 xl:col-span-6 lg:row-start-1 lg:row-end-2"> <div ref={ref} className="relative z-10 bg-white rounded-xl shadow-xl ring-1 ring-slate-900/5 dark:bg-slate-800 dark:highlight-white/10" > <article> <h2 className={clsx( 'text-lg font-semibold text-slate-900 pt-4 pb-2 px-4 sm:px-6 lg:px-4 xl:px-6 dark:text-slate-100', ...fade )} > Weekly one-on-one </h2> <dl className="flex flex-wrap divide-y divide-slate-200 border-b border-slate-200 text-sm sm:text-base lg:text-sm xl:text-base dark:divide-slate-200/5 dark:border-slate-200/5"> <div className="px-4 sm:px-6 lg:px-4 xl:px-6 pb-4"> <dt className="sr-only">Date and time</dt> <dd className={clsx(...fade)}> <time dateTime="2020-11-15T10:00:00-05:00">Thu Nov 15, 2020 10:00am</time> -{' '} <time dateTime="2020-11-15T11:00:00-05:00"> 11:00am<span className="sr-only sm:not-sr-only"> EST</span> </time> </dd> </div> <div className="w-full flex-none flex items-center p-4 sm:p-6 lg:p-4 xl:p-6"> <dt className={clsx( 'w-2/5 sm:w-1/4 flex-none text-slate-900 font-medium dark:text-slate-300', ...fade )} > Location </dt> <dd className={clsx(...fade)}> Kitchener, <abbr title="Ontario">ON</abbr> </dd> </div> <div className="w-full flex-none flex items-center p-4 sm:p-6 lg:p-4 xl:p-6"> <dt className={clsx( 'w-2/5 sm:w-1/4 flex-none text-slate-900 font-medium dark:text-slate-300', ...fade )} > Description </dt> <dd className={clsx('italic', ...fade)}>No meeting description</dd> </div> <div className="w-full flex-none flex items-center p-4 sm:py-5 sm:px-6 lg:p-4 xl:py-5 xl:px-6"> <dt className={clsx( 'w-2/5 sm:w-1/4 flex-none text-slate-900 font-medium dark:text-slate-300', ...fade )} > Attendees </dt> <dd className={clsx( 'text-sm font-medium text-slate-700 bg-slate-100 rounded-full py-1 px-3 dark:bg-slate-700 dark:text-slate-300', ...fade )} > Andrew McDonald </dd> </div> </dl> <div className="grid grid-cols-2 gap-x-4 sm:gap-x-6 lg:gap-x-4 xl:gap-x-6 p-4 sm:px-6 sm:py-5 lg:p-4 xl:px-6 xl:py-5"> <div className="text-base font-medium rounded-lg bg-slate-100 text-slate-900 py-3 text-center cursor-pointer dark:bg-slate-600 dark:text-slate-400 dark:highlight-white/10"> Decline </div> <div className="text-base font-medium rounded-lg bg-sky-500 text-white py-3 text-center cursor-pointer dark:highlight-white/20"> Accept </div> </div> </article> </div> </div> <div className="mt-4 -mx-4 sm:mx-0 lg:mt-0 lg:col-span-7 lg:row-end-2 xl:mt-18 xl:col-span-6 xl:row-span-2"> <ApplyExample inView={inView} /> </div> </div> ) } const tabItems = { React: (selected) => ( <> <path d="M30.685 27.536c-5.353 9.182-12.462 15.042-15.878 13.089-3.416-1.953-1.846-10.98 3.508-20.161 5.353-9.182 12.462-15.042 15.878-13.089 3.416 1.953 1.846 10.98-3.508 20.161Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <ellipse cx="24" cy="24" rx="7" ry="19" transform="rotate(90 24 24)" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <path d="M17.315 27.536c5.353 9.182 12.462 15.042 15.878 13.089 3.416-1.953 1.846-10.98-3.508-20.161-5.353-9.182-12.462-15.042-15.878-13.089-3.416 1.953-1.846 10.98 3.508 20.161Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <path d="M24 27a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" fill={selected ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" /> </> ), Vue: (selected) => ( <> <path d="M24 12.814 20.474 7H15l9 15 9-15h-5.476l-3.525 5.814Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" strokeLinejoin="round" /> <path d="M37.408 7 24 28.982 10.592 7H3l21 34L45 7h-7.592Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" strokeLinejoin="round" /> </> ), Angular: (selected) => ( <> <path d="M10 35 7 12l17-7 17 7-3 23-14 8-14-8Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" strokeLinejoin="round" /> <path d="M20 25h8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path fillRule="evenodd" clipRule="evenodd" d="M32.617 31 24 13.764 15.381 31h2.236l6.382-12.764L30.381 31h2.236Z" fill="currentColor" /> </> ), Blade: (selected) => ( <> <path d="m7.5 10.5 6.5-3 7 3.5v16l7-4v-8l7-4 7 4v8l-7 3.5V34l-14 7.5L7.5 34V10.5Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} /> <path d="m7 11 7-4 7 4-7 4-7-4ZM21 11v16M21 35v7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M7 11v23l14 8 14-8V19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M14 15v16l7 4 21-12v-8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="m28 15 7-4 7 4-7 4-7-4ZM28 15v8l7 4M14 31l14-8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </> ), } export function ComponentDriven() { const [framework, setFramework] = useState('React') return ( <section id="component-driven"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-sky-500 dark:highlight-white/20" light={require('@/img/icons/home/component-driven.png').default.src} dark={require('@/img/icons/home/dark/component-driven.png').default.src} /> <Caption className="text-sky-500">Component-driven</Caption> <BigText> <Widont>Worried about duplication? Don’t be.</Widont> </BigText> <Paragraph> If you're repeating the same utilities over and over and over again, all you have to do is extract them into a component or template partial and boom — you've got a single source of truth so you can make changes in one place. </Paragraph> <Link href="/docs/reusing-styles" color="sky" darkColor="gray"> Learn more<span className="sr-only">, reusing styles</span> </Link> <div className="mt-10"> <Tabs tabs={tabItems} className="text-sky-500" iconClassName="text-sky-500" grid={true} spacing="loose" selected={framework} onChange={setFramework} /> </div> </div> <GridLockup.Container className="mt-10 xl:mt-2" beams={8}> <GridLockup.Grid left={ <div className="relative z-10 bg-white rounded-xl shadow-xl ring-1 ring-slate-900/5 divide-y divide-slate-100 my-auto xl:mt-18 dark:bg-slate-800 dark:divide-slate-200/5 dark:highlight-white/10"> <nav className="py-4 px-4 sm:px-6 lg:px-4 xl:px-6 text-sm font-medium"> <ul className="flex space-x-3"> <li> <div className="px-3 py-2 rounded-md bg-sky-500 text-white cursor-pointer"> New<span className="hidden sm:inline lg:hidden xl:inline"> Releases</span> </div> </li> <li> <div className="px-3 py-2 rounded-md bg-slate-50 cursor-pointer dark:bg-transparent dark:text-slate-300 dark:ring-1 dark:ring-slate-700"> Top<span className="hidden sm:inline"> Rated</span> </div> </li> <li> <div className="px-3 py-2 rounded-md bg-slate-50 cursor-pointer dark:bg-transparent dark:text-slate-300 dark:ring-1 dark:ring-slate-700"> Vincent’s Picks </div> </li> </ul> </nav> {movies.map(({ title, starRating, rating, year, genre, runtime, cast, image }, i) => ( <article key={title} className={clsx( 'p-4 sm:p-6 lg:p-4 xl:p-6 space-x-4 items-start sm:space-x-6 lg:space-x-4 xl:space-x-6', i < 2 ? 'flex' : 'hidden sm:flex' )} > <img src={image} loading="lazy" decoding="async" alt="" width="60" height="88" className="flex-none rounded-md bg-slate-100" /> <div className="min-w-0 relative flex-auto"> <h2 className="font-semibold text-slate-900 truncate sm:pr-20 dark:text-slate-100"> {title} </h2> <dl className="mt-2 flex flex-wrap text-sm leading-6 font-medium"> <div className="hidden absolute top-0 right-0 sm:flex items-center space-x-1 dark:text-slate-100"> <dt className="text-sky-500"> <span className="sr-only">Star rating</span> <svg width="16" height="20" fill="currentColor"> <path d="M7.05 3.691c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.372 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.539 1.118l-2.8-2.034a1 1 0 00-1.176 0l-2.8 2.034c-.783.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.363-1.118L.98 9.483c-.784-.57-.381-1.81.587-1.81H5.03a1 1 0 00.95-.69L7.05 3.69z" /> </svg> </dt> <dd>{starRating}</dd> </div> <div className="dark:text-slate-200"> <dt className="sr-only">Rating</dt> <dd className="px-1.5 ring-1 ring-slate-200 rounded dark:ring-slate-600"> {rating} </dd> </div> <div className="ml-2"> <dt className="sr-only">Year</dt> <dd>{year}</dd> </div> <div> <dt className="sr-only">Genre</dt> <dd className="flex items-center"> <svg width="2" height="2" fill="currentColor" className="mx-2 text-slate-300" aria-hidden="true" > <circle cx="1" cy="1" r="1" /> </svg> {genre} </dd> </div> <div> <dt className="sr-only">Runtime</dt> <dd className="flex items-center"> <svg width="2" height="2" fill="currentColor" className="mx-2 text-slate-300" aria-hidden="true" > <circle cx="1" cy="1" r="1" /> </svg> {runtime} </dd> </div> <div className="flex-none w-full mt-2 font-normal"> <dt className="sr-only">Cast</dt> <dd className="text-slate-400">{cast}</dd> </div> </dl> </div> </article> ))} </div> } right={<ComponentExample framework={framework} />} /> <AtApplySection /> </GridLockup.Container> </section> ) }
DarkMode.js
import { useState } from 'react' import { Switch } from '@headlessui/react' import { Paragraph, IconContainer, Caption, BigText, Link, Widont, InlineCode, } from '@/components/home/common' import { CodeWindow } from '@/components/CodeWindow' import { addClassTokens } from '@/utils/addClassTokens' import { Token } from '@/components/Code' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { code, tokens } from '../../samples/dark-mode.html?highlight' function Sun(props) { return ( <svg width="24" height="24" fill="none" aria-hidden="true" {...props}> <path d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M12 4v1M18 6l-1 1M20 12h-1M18 18l-1-1M12 19v1M7 17l-1 1M5 12H4M7 7 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> ) } function Moon(props) { return ( <svg width="24" height="24" fill="none" aria-hidden="true" {...props}> <path d="M18 15.63c-.977.52-1.945.481-3.13.481A6.981 6.981 0 0 1 7.89 9.13c0-1.185-.04-2.153.481-3.13C6.166 7.174 5 9.347 5 12.018A6.981 6.981 0 0 0 11.982 19c2.67 0 4.844-1.166 6.018-3.37ZM16 5c0 2.08-.96 4-3 4 2.04 0 3 .92 3 3 0-2.08.96-3 3-3-2.04 0-3-1.92-3-4Z" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> ) } function DarkModeSwitch({ enabled, onChange }) { return ( <Switch checked={enabled} onChange={onChange} className={clsx( 'relative inline-flex items-center py-1.5 px-2 rounded-full transition-colors duration-300 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus:outline-none', enabled ? 'bg-slate-700 text-slate-400 focus-visible:ring-slate-500' : 'bg-cyan-500 text-cyan-200 focus-visible:ring-cyan-600' )} > <span className="sr-only">{enabled ? 'Enable' : 'Disable'} dark mode</span> <Sun className={clsx( 'transform transition-transform', enabled ? 'scale-100 duration-300' : 'scale-0 duration-500' )} /> <Moon className={clsx( 'ml-3.5 transform transition-transform', enabled ? 'scale-0 duration-500' : 'scale-100 duration-300' )} /> <span className={clsx( 'absolute top-0.5 left-0.5 bg-white w-8 h-8 rounded-full flex items-center justify-center transition duration-500 transform', enabled ? 'translate-x-[2.625rem]' : '' )} > <Sun className={clsx( 'flex-none transition duration-500 transform text-cyan-500', enabled ? 'opacity-0 scale-0' : 'opacity-100 scale-100' )} /> <Moon className={clsx( 'flex-none -ml-6 transition duration-500 transform text-slate-700', enabled ? 'opacity-100 scale-100' : 'opacity-0 scale-0' )} /> </span> </Switch> ) } export function DarkMode() { const [enabled, setEnabled] = useState(false) return ( <section id="dark-mode"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-slate-600 dark:highlight-white/20" light={require('@/img/icons/home/dark-mode.png').default.src} dark={require('@/img/icons/home/dark/dark-mode.png').default.src} /> <Caption className="text-slate-500">Dark mode</Caption> <BigText> <Widont>Now with Dark Mode.</Widont> </BigText> <Paragraph> Don’t want to be one of those websites that blinds people when they open it on their phone at 2am? Enable dark mode in your configuration file then throw{' '} <InlineCode>dark:</InlineCode> in front of any color utility to apply it when dark mode is active. Works for background colors, text colors, border colors, and even gradients. </Paragraph> <Link href="/docs/dark-mode" color="gray"> Learn more<span className="sr-only">, dark mode</span> </Link> </div> <GridLockup className="mt-10 xl:mt-2" beams={5} left={ <div className="relative xl:mt-18"> <DarkModeSwitch enabled={enabled} onChange={setEnabled} /> <div className={clsx('mt-6 sm:mt-10 relative z-10 rounded-xl shadow-xl', { 'demo-dark': enabled, })} dangerouslySetInnerHTML={{ __html: code .replace(/\(light\)/g, '') .replace(/demo-dark:/g, 'transition-all duration-500 demo-dark:') .replace( 'src="/full-stack-radio.png"', `src="${ require('@/img/full-stack-radio.png').default.src }" loading="lazy" decoding="async" ` ), // .replace(/<button type="button" class="/g, '<div class="cursor-pointer ') // .replace(/<\/button>/g, '</div>'), }} /> </div> } right={ <CodeWindow> <CodeWindow.Code tokens={tokens} tokenComponent={DarkModeToken} tokenProps={{ enabled }} transformTokens={addClassTokens} /> </CodeWindow> } /> </section> ) } function DarkModeToken({ token, parentTypes, enabled, children }) { if (token[0] === 'class') { if (token[1].startsWith('demo-dark:')) { return ( <span className={clsx('code-highlight transition-colors duration-500', { 'bg-code-highlight': enabled, })} > {token[1].replace(/^demo-dark:/, 'dark:')} </span> ) } if (token[1].startsWith('(light)')) { return ( <span className={clsx('transition-opacity duration-500', { 'opacity-50': enabled })}> {token[1].replace(/^\(light\)/, '')} </span> ) } } return ( <Token token={token} parentTypes={parentTypes}> {children} </Token> ) }
Customization.js
import { IconContainer, Caption, BigText, Paragraph, Link, Widont, themeTabs, } from '@/components/home/common' import { Tabs } from '@/components/Tabs' import { CodeWindow } from '@/components/CodeWindow' import { useEffect, useRef, useState } from 'react' import tailwindColors from 'tailwindcss/colors' import { AnimatePresence, motion } from 'framer-motion' import { font as pallyVariable } from '../../fonts/generated/Pally-Variable.module.css' import { font as sourceSerifProRegular } from '../../fonts/generated/SourceSerifPro-Regular.module.css' import { font as ibmPlexMonoRegular } from '../../fonts/generated/IBMPlexMono-Regular.module.css' import { font as synonymVariable } from '../../fonts/generated/Synonym-Variable.module.css' import { Token } from '../Code' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { tokens } from '../../samples/customization.js?highlight' const defaultSampleBody = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut augue gravida cras quis ac duis pretium ullamcorper consequat. Integer pellentesque eu.' const themes = { Simple: { font: 'Inter', fontStacks: [ ['Inter', 'system-ui', 'sans-serif'], ['Inter', 'system-ui', 'sans-serif'], ], bodySize: '14pt', colors: { primary: 'blue', secondary: 'slate', }, }, Playful: { font: 'Pally', fontStacks: [ ['Pally', 'Comic Sans MS', 'sans-serif'], ['Pally', 'Comic Sans MS', 'sans-serif'], ], bodySize: '14pt', classNameDisplay: `${pallyVariable} font-medium`, classNameBody: pallyVariable, colors: { primary: 'rose', secondary: 'violet', }, }, Elegant: { font: 'Source Serif Pro', fontStacks: [ ['Source Serif Pro', 'Georgia', 'serif'], ['Synonym', 'system-ui', 'sans-serif'], ], bodySize: '14pt', classNameDisplay: sourceSerifProRegular, classNameBody: synonymVariable, colors: { primary: 'slate', secondary: 'emerald', }, }, Brutalist: { font: 'IBM Plex Mono', fontStacks: [ ['IBM Plex Mono', 'Menlo', 'monospace'], ['IBM Plex Mono', 'Menlo', 'monospace'], ], bodySize: '14pt', classNameDisplay: ibmPlexMonoRegular, classNameBody: ibmPlexMonoRegular, sampleBody: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut augue gravida cras quis ac duis pretium ullamcorper consequat.', colors: { primary: 'gray', secondary: 'teal', }, }, } export function Customization() { const [theme, setTheme] = useState('Simple') return ( <section id="customization"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-pink-500 dark:highlight-white/30" light={require('@/img/icons/home/customization.png').default.src} dark={require('@/img/icons/home/dark/customization.png').default.src} /> <Caption className="text-pink-500 dark:text-pink-400">Customization</Caption> <BigText> <Widont>Extend it, tweak it, change it.</Widont> </BigText> <Paragraph as="div"> <p> Tailwind includes an expertly crafted set of defaults out-of-the-box, but literally everything can be customized — from the color palette to the spacing scale to the box shadows to the mouse cursor. </p> <p> Use the tailwind.config.js file to craft your own design system, then let Tailwind transform it into your own custom CSS framework. </p> </Paragraph> <Link href="/docs/configuration" color="pink" darkColor="gray"> Learn more<span className="sr-only">, configuration</span> </Link> <div className="mt-10"> <Tabs tabs={themeTabs} selected={theme} onChange={setTheme} className="text-pink-500 dark:text-pink-400" iconClassName="text-pink-500 dark:text-pink-400" /> </div> </div> <GridLockup className="mt-10 xl:mt-2" beams={6} left={ <div className="relative z-10 bg-white ring-1 ring-slate-900/5 rounded-lg shadow-xl px-6 py-5 my-auto xl:mt-18 dark:bg-slate-800"> <div className="absolute inset-x-0 inset-y-5 border-t border-b border-slate-100 pointer-events-none dark:border-slate-700" /> <div className="absolute inset-x-6 inset-y-0 border-l border-r border-slate-100 pointer-events-none dark:border-slate-700" /> <div className="bg-slate-50 overflow-hidden py-6 sm:py-9 lg:py-6 xl:py-9 px-6 dark:bg-slate-900/50"> <div className="sm:flex lg:block xl:flex"> <div className="relative flex-auto flex min-w-0"> <div className="w-full flex-none"> <h3 className="sr-only">Typography</h3> <ul className="space-y-8"> <li> <dl className="grid"> <div className="font-mono text-xs leading-5 pb-1 border-b border-slate-200 text-slate-500 dark:border-slate-200/10"> <dt className="sr-only">CSS class</dt> <dd>font-display</dd> </div> <div className="col-start-2 text-right font-mono text-xs leading-5 text-slate-400 pb-1 border-b border-slate-200 dark:text-slate-500 dark:border-slate-200/10"> <dt className="sr-only">Font name</dt> <AnimatePresence initial={false} exitBeforeEnter> <motion.dd key={themes[theme].font} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > {themes[theme].font} </motion.dd> </AnimatePresence> </div> <div className="mt-4 col-span-2 text-4xl sm:text-5xl lg:text-4xl xl:text-5xl text-slate-900 dark:text-slate-200"> <dt className="sr-only">Sample</dt> <AnimatePresence initial={false} exitBeforeEnter> <motion.dd key={theme} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className={themes[theme].classNameDisplay} > AaBbCc </motion.dd> </AnimatePresence> </div> </dl> </li> <li> <dl className="grid"> <div className="font-mono text-xs leading-5 pb-1 border-b border-slate-200 text-slate-500 dark:border-slate-200/10"> <dt className="sr-only">CSS class</dt> <dd>font-body</dd> </div> <div className="col-start-2 text-right font-mono text-xs leading-5 text-slate-400 pb-1 border-b border-slate-200 dark:text-slate-500 dark:border-slate-200/10"> <dt className="sr-only">Font size</dt> <AnimatePresence initial={false} exitBeforeEnter> <motion.dd key={themes[theme].bodySize} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > {themes[theme].bodySize} </motion.dd> </AnimatePresence> </div> <div className="mt-4 col-span-2 text-sm leading-6 text-slate-700 dark:text-slate-400"> <dt className="sr-only">Sample</dt> <AnimatePresence initial={false} exitBeforeEnter> <motion.dd key={theme} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className={themes[theme].classNameBody} > {themes[theme].sampleBody || defaultSampleBody} </motion.dd> </AnimatePresence> </div> </dl> </li> </ul> </div> <div aria-hidden="true" className="w-full flex-none -ml-full pointer-events-none pt-[10.125rem] flex text-sm leading-6 invisible" > {Object.keys(themes).map((theme, i) => ( <div key={theme} className={clsx( 'w-full flex-none', i > 0 && '-ml-full', themes[theme].classNameBody )} > {themes[theme].sampleBody || defaultSampleBody} </div> ))} </div> </div> <div className="flex-none mt-6 sm:mt-0 lg:mt-6 xl:mt-0 sm:ml-10 lg:ml-0 xl:ml-10"> <h3 className="sr-only">Colors</h3> <ul className="space-y-6"> {Object.entries(themes[theme].colors).map(([name, color], index) => ( <li key={name} className={index === 0 ? undefined : 'hidden sm:block lg:hidden xl:block'} > <dl className="grid bg-white text-slate-500 rounded-lg shadow-md p-3 dark:bg-slate-900 dark:ring-1 dark:ring-white/10"> <div className="font-mono text-xs"> <dt className="sr-only">CSS class prefix</dt> <dd>bg-{name}</dd> </div> <div className="col-start-2 font-mono text-xs text-right"> <dt className="sr-only">Range</dt> <dd>50-900</dd> </div> <div className="mt-4 col-span-2"> <dt className="sr-only">Sample</dt> <dd> <ul className="grid grid-cols-5 gap-2"> {[50, 100, 200, 300, 400, 500, 600, 700, 800, 900].map((key) => ( <motion.li key={key} className="pt-full sm:w-8 lg:w-auto xl:w-8 rounded-sm ring-1 ring-inset ring-slate-900/5 dark:ring-0 dark:highlight-white/10" initial={false} animate={{ backgroundColor: tailwindColors[color][key], }} /> ))} </ul> </dd> </div> </dl> </li> ))} </ul> </div> </div> </div> </div> } right={ <CodeWindow> <CodeWindow.Code tokens={tokens} tokenComponent={CustomizationToken} tokenProps={{ theme }} transformTokens={(token) => { if (typeof token === 'string' && token.includes('__SECONDARY_COLOR__')) { return ['__SECONDARY_COLOR__', token] } return token }} /> </CodeWindow> } /> </section> ) } function CustomizationToken({ theme, ...props }) { const { token } = props const initial = useRef(true) useEffect(() => { initial.current = false }, []) if (token[0] === 'string' && token[1].startsWith("'font-")) { let [i, j] = token[1].match(/[0-9]+/g).map((x) => parseInt(x, 10)) return ( <span className="token string"> {"'"} <span className={clsx('code-highlight', { 'animate-flash-code': !initial.current })} key={themes[theme].fontStacks[i][j]} > {themes[theme].fontStacks[i][j]} </span> {"'"} </span> ) } if (token[0] === 'string' && token[1].startsWith("'color-")) { const [, name, shade] = token[1].substr(1, token[1].length - 2).split('-') const color = tailwindColors[themes[theme].colors[name]][shade] return ( <span className="token string"> {"'"} <span className={clsx('code-highlight', { 'animate-flash-code': !initial.current })} key={color} > {color} </span> {"'"} </span> ) } if (token[0] === '__SECONDARY_COLOR__') { let name = Object.keys(themes[theme].colors)[1] return token[1].split('__SECONDARY_COLOR__').map((part, i) => i % 2 === 0 ? ( part ) : ( <span className={clsx('code-highlight', { 'animate-flash-code': !initial.current })} key={name} > {name} </span> ) ) } return <Token {...props} /> }
ModernFeatures.js
import { IconContainer, Caption, BigText, Paragraph, Link, Widont, InlineCode, } from '@/components/home/common' import { Tabs } from '@/components/Tabs' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { Fragment, useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' import clsx from 'clsx' import { GridLockup } from '../GridLockup' import { lines as gridSample } from '../../samples/grid.html?highlight' import { lines as transformsSample } from '../../samples/transforms.html?highlight' import { lines as filtersSample } from '../../samples/filters.html?highlight' const lines = { 'CSS Grid': gridSample, Transforms: transformsSample, Filters: filtersSample, } const tabs = { 'CSS Grid': (selected) => ( <> <path d="M5 13a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-6ZM5 29a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-6ZM19 29a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-4a3 3 0 0 1-3-3v-6ZM33 29a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-4a3 3 0 0 1-3-3v-6ZM19 13a3 3 0 0 1 3-3h18a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H22a3 3 0 0 1-3-3v-6Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> </> ), Transforms: (selected) => ( <> <path d="M5.632 11.725a3 3 0 0 1 2.554-3.388l3.96-.557a3 3 0 0 1 3.389 2.554l.835 5.941a3 3 0 0 1-2.553 3.388l-3.961.557a3 3 0 0 1-3.389-2.553l-.835-5.942ZM1 29a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3v-6ZM20 34a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-4a3 3 0 0 1-3-3v-6ZM36.728 27.026a3 3 0 0 1 3.558-2.31l3.913.831a3 3 0 0 1 2.31 3.558l-1.247 5.87a3 3 0 0 1-3.558 2.31l-3.913-.832a3 3 0 0 1-2.31-3.558l1.247-5.869ZM22.236 9.17a3 3 0 0 1 3.202-2.783l17.956 1.255a3 3 0 0 1 2.784 3.202l-.419 5.986a3 3 0 0 1-3.202 2.783l-17.956-1.255a3 3 0 0 1-2.784-3.202l.419-5.986Z" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> </> ), Filters: (selected) => ( <> <path d="M31 30c0-7.18-5.82-13-13-13m-5.009 1C8.298 19.961 5 24.596 5 30c0 7.18 5.82 13 13 13 5.404 0 10.039-3.298 12-7.991" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <circle cx="30" cy="18" r="13" fill="currentColor" fillOpacity={selected ? '.1' : '0'} stroke="currentColor" strokeWidth="2" /> <path d="m26 30 4-4M21 27l6-6M18 22l4-4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </> ), } function Block({ src, filter, ...props }) { return ( <motion.div initial={false} {...props}> <div className={clsx( 'relative pt-full bg-white rounded-lg shadow-lg overflow-hidden transition-[filter] duration-500', filter )} > <img src={src} alt="" className="absolute inset-0 w-full h-full object-cover" decoding="async" loading="lazy" /> </div> </motion.div> ) } export function ModernFeatures() { const [feature, setFeature] = useState('CSS Grid') const animate = (transforms, grid) => { if (feature === 'Transforms') { return { animate: transforms, } } return { animate: grid, } } return ( <section id="modern-features"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-indigo-500 dark:highlight-white/20" light={require('@/img/icons/home/modern-features.png').default.src} dark={require('@/img/icons/home/dark/modern-features.png').default.src} /> <Caption className="text-indigo-500 dark:text-indigo-400">Modern features</Caption> <BigText> <Widont>Cutting-edge is our comfort zone.</Widont> </BigText> <Paragraph as="div"> <p> Tailwind is unapologetically modern, and takes advantage of all the latest and greatest CSS features to make the developer experience as enjoyable as possible. </p> <p> We've got first-class CSS grid support, composable transforms and gradients powered by CSS variables, support for modern state selectors like{' '} <InlineCode>:focus-visible</InlineCode>, and tons more. </p> </Paragraph> <Link href="/docs/grid-template-columns" color="indigo" darkColor="gray"> Learn more<span className="sr-only">, grid template columns</span> </Link> <div className="mt-10"> <Tabs tabs={tabs} selected={feature} onChange={setFeature} className="text-indigo-600 dark:text-indigo-400" iconClassName="text-indigo-500 dark:text-indigo-400" /> </div> </div> <GridLockup className="mt-10 xl:mt-2" beams={0} left={ <div className="flex text-4xl font-black lg:mt-10 xl:mt-18"> <div className="w-full flex-none grid grid-cols-3 grid-rows-2 gap-8"> <Block src={require('@/img/modern-features/1.jpg').default.src} filter={feature === 'Filters' && 'blur'} {...animate( { scaleX: 1.1, scaleY: 1.1, rotate: -6, x: 0, y: 0 }, { scaleX: 1, scaleY: 1, rotate: 0, x: 0, y: 0 } )} /> <Block className="col-start-3 col-end-4 row-start-2 row-end-3" src={require('@/img/modern-features/2.jpg').default.src} filter={feature === 'Filters' && 'sepia'} {...animate( { scaleX: 0.75, scaleY: 0.75, rotate: 6, y: 60, x: 8, }, { scaleX: 1, scaleY: 1, rotate: 0, y: 0, x: 0 } )} /> <Block className="origin-right" src={require('@/img/modern-features/3.jpg').default.src} filter={feature === 'Filters' && 'saturate-200'} {...animate( { scaleX: 1.5, x: 0, y: 44, rotate: 0 }, { scaleX: 1, x: 0, y: 0, rotate: 0 } )} /> <Block src={require('@/img/modern-features/4.jpg').default.src} filter={feature === 'Filters' && 'grayscale'} {...animate({ x: 0, y: 96, rotate: 0 }, { x: 0, y: 0, rotate: 0 })} /> <motion.div className={clsx( 'relative bg-white rounded-lg shadow-lg overflow-hidden col-start-2 col-end-4 row-start-1 row-end-2 transition-[filter] duration-500', feature === 'Filters' && 'invert' )} initial={false} {...animate( { opacity: 1, x: 80, y: 16, rotate: 0 }, { opacity: 1, x: 0, y: 0, rotate: 0 } )} > <img src={require('@/img/modern-features/5.jpg').default.src} alt="" className="absolute inset-0 w-full h-full object-cover" decoding="async" loading="lazy" /> </motion.div> </div> </div> } right={ <CodeWindow> <AnimatePresence initial={false} exitBeforeEnter> <motion.div key={feature} className="w-full flex-auto flex min-h-0" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > <CodeWindow.Code2 lines={lines[feature].length}> {lines[feature].map((tokens, lineIndex) => ( <Fragment key={lineIndex}> {tokens.map((token, tokenIndex) => { if (token.types[token.types.length - 1] === 'attr-value') { return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content.split(/\[([^\]]+)\]/).map((part, i) => i % 2 === 0 ? ( part ) : ( <span key={i} className="code-highlight bg-code-highlight"> {part} </span> ) )} </span> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} {'\n'} </Fragment> ))} </CodeWindow.Code2> </motion.div> </AnimatePresence> </CodeWindow> } /> </section> ) }
EditorTools.js
import { IconContainer, Caption, BigText, Paragraph, Link } from '@/components/home/common' import { CodeWindow, getClassNameForToken } from '@/components/CodeWindow' import { motion } from 'framer-motion' import { Fragment, useEffect, useState } from 'react' import { useInView } from 'react-intersection-observer' import colors from 'tailwindcss/colors' import dlv from 'dlv' import { GridLockup } from '../GridLockup' import { lines } from '../../samples/editor-tools.html?highlight' import clsx from 'clsx' const problems = [ ["'flex' applies the same CSS property as 'block'.", 'cssConflict [1, 20]'], ["'block' applies the same CSS property as 'flex'.", 'cssConflict [1, 54]'], ] const completions = [ // ['sm:', '@media (min-width: 640px)'], ['md:'], ['lg:'], ['xl:'], ['focus:'], ['group-hover:'], ['hover:'], ['container'], ['space-y-0'], ['space-x-0'], ['space-y-1'], ['space-x-1'], // ['bg-fixed', 'background-position: fixed;'], ['bg-local'], ['bg-scroll'], ['bg-clip-border'], ['bg-clip-padding'], ['bg-clip-content'], ['bg-clip-text'], ['bg-transparent', 'background-color: transparent;'], ['bg-current'], ['bg-black', '#000'], ['bg-white', '#fff'], ['bg-gray-50', colors.gray[50]], // ['bg-teal-50', `background-color: ${colors.teal[50]};`, colors.teal[50]], ['bg-teal-100', `background-color: ${colors.teal[100]};`, colors.teal[100]], ['bg-teal-200', `background-color: ${colors.teal[200]};`, colors.teal[200]], ['bg-teal-300', `background-color: ${colors.teal[300]};`, colors.teal[300]], ['bg-teal-400', `background-color: ${colors.teal[400]};`, colors.teal[400]], ['bg-teal-500', undefined, colors.teal[500]], ['bg-teal-600', undefined, colors.teal[600]], ['bg-teal-700', undefined, colors.teal[700]], ['bg-teal-800', undefined, colors.teal[800]], ['bg-teal-900', undefined, colors.teal[900]], ['bg-top'], ] function CompletionDemo() { const { ref, inView } = useInView({ threshold: 0.5, triggerOnce: true }) return ( <CodeWindow.Code2 ref={ref} lines={lines.length} overflow={false} className="overflow-hidden"> {lines.map((tokens, lineIndex) => ( <Fragment key={lineIndex}> {tokens.map((token, tokenIndex) => { if (token.content === '__CONFLICT__') { return ( <span key={tokenIndex} className={getClassNameForToken(token)}> w-full{' '} <span className="inline-flex bg-squiggle bg-repeat-x bg-left-bottom">flex</span>{' '} items-center justify-between{' '} <span className="inline-flex bg-squiggle bg-repeat-x bg-left-bottom">block</span>{' '} p-6 space-x-6 </span> ) } if (token.content === '__COMPLETION__') { return <Completion key={tokenIndex} inView={inView} /> } if ( token.types[token.types.length - 1] === 'attr-value' && tokens[tokenIndex - 3].content === 'class' ) { return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content.split(' ').map((c, i) => { const space = i === 0 ? '' : ' ' if (/^(bg|text|border)-/.test(c)) { const color = dlv(colors, c.replace(/^(bg|text|border)-/, '').split('-')) if (color) { return ( <Fragment key={i}> {space} <ColorDecorator color={color} /> {c} </Fragment> ) } } return space + c })} </span> ) } return ( <span key={tokenIndex} className={getClassNameForToken(token)}> {token.content} </span> ) })} {'\n'} </Fragment> ))} </CodeWindow.Code2> ) } function Completion({ inView }) { const [typed, setTyped] = useState('') const [selectedCompletionIndex, setSelectedCompletionIndex] = useState(0) const [stage, setStage] = useState(-1) useEffect(() => { if (inView) { setStage(0) } }, [inView]) useEffect(() => { if (typed === ' bg-t') { let i = 0 let id = window.setInterval(() => { if (i === 5) { setStage(1) setTyped('') setSelectedCompletionIndex(0) return window.clearInterval(id) } i++ setSelectedCompletionIndex(i) }, 300) return () => window.clearInterval(id) } }, [typed]) useEffect(() => { let id if (stage === 1) { id = window.setTimeout(() => { setStage(2) }, 2000) } else if (stage === 2 || stage === 3 || stage === 4 || stage === 5) { id = window.setTimeout(() => { setStage(stage + 1) }, 300) } else if (stage === 6) { id = window.setTimeout(() => { setStage(-1) setStage(0) }, 2000) } return () => { window.clearTimeout(id) } }, [stage]) return ( <span className="token attr-value"> <span className="hidden sm:inline-flex items-baseline"> <ColorDecorator color={colors.teal[600]} /> text-teal-600 {stage >= 1 && stage < 2 && ' '} </span> {stage >= 1 && stage < 2 && <ColorDecorator color={colors.teal[400]} />} {stage >= 0 && stage < 2 && ' bg-t' .split('') .filter((char) => (stage >= 1 && stage < 6 ? char !== ' ' : true)) .map((char, i) => ( <span key={char} className={char === ' ' ? 'hidden sm:inline' : undefined}> <motion.span initial={{ display: 'none' }} animate={{ display: 'inline' }} transition={{ delay: (i + 1) * 0.3 }} onAnimationComplete={() => setTyped(typed + char)} > {char} </motion.span> </span> ))} {stage === 1 && 'eal-400'} {(stage < 2 || stage === 6) && <span className="border -mx-px h-5" />} {stage >= 2 && stage <= 5 && ( <Fragment key={stage}> <span className="hidden sm:inline">{stage < 5 && ' '}</span> {stage < 5 && <ColorDecorator color={colors.teal[400]} />} {stage >= 4 && <span className="relative border -mx-px h-5" />} {stage === 5 && ( <> <span className="inline-flex items-baseline" style={{ background: 'rgba(81, 92, 126, 0.4)' }} > <span className="hidden sm:inline"> </span> <ColorDecorator color={colors.teal[400]} /> </span> </> )} <span className="inline-flex" style={{ background: stage >= 4 ? 'rgba(81, 92, 126, 0.4)' : undefined }} > bg- </span> {stage === 3 && <span className="relative border -mx-px h-5" />} <span className="inline-flex" style={{ background: stage >= 3 ? 'rgba(81, 92, 126, 0.4)' : undefined }} > teal- </span> {stage === 2 && <span className="relative border -mx-px h-5" />} <span className="inline-flex" style={{ background: stage >= 2 ? 'rgba(81, 92, 126, 0.4)' : undefined }} > 400 </span> </Fragment> )} {typed && ( <span className="relative z-10"> <div className="absolute top-full left-full m-0.5 -ml-16 sm:ml-0.5 rounded-lg shadow-xl"> <div className="relative w-96 bg-slate-700 overflow-hidden rounded-lg"> <ul className="relative leading-5 text-white py-3"> {completions .filter((completion) => completion[0].startsWith(typed.trim())) .slice(0, 12) .map((completion, i) => ( <li key={completion[0]} className={clsx('px-3 flex items-center space-x-2', { 'bg-slate-600': i === selectedCompletionIndex, })} > <span className="w-5 flex-none flex justify-center"> {completion[2] ? ( <span className="flex-none w-3 h-3 rounded ring-1 ring-slate-900/30" style={{ background: completion[2] }} /> ) : ( <svg className="w-5 h-5"> <path className="!text-slate-400" fill="currentColor" fillRule="evenodd" clipRule="evenodd" d="M16 5H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1ZM4 4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4Zm1 4h10v1H5V8Zm10 3H5v1h10v-1Z" /> </svg> )} </span> <span className="flex-none !text-slate-50"> {completion[0].split(new RegExp(`(^${typed.trim()})`)).map((part, j) => part ? ( j % 2 === 0 ? ( part ) : ( <span key={j} className="!text-teal-200"> {part} </span> ) ) : null )} </span> {i === selectedCompletionIndex && completion[1] ? ( <span className="hidden sm:block flex-auto text-right !text-slate-400 truncate pl-4"> {completion[1]} </span> ) : null} </li> ))} </ul> </div> </div> </span> )} </span> ) } function ColorDecorator({ color }) { return ( <span className="inline-flex w-3 h-3 rounded ring-1 ring-slate-900/30 relative top-px mr-1" style={{ background: color }} /> ) } export function EditorTools() { return ( <section id="editor-tools"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-sky-500 dark:highlight-white/20" light={require('@/img/icons/home/editor-tools.png').default.src} dark={require('@/img/icons/home/dark/editor-tools.png').default.src} /> <Caption className="text-sky-500">Editor tools</Caption> <BigText>World-class IDE integration.</BigText> <Paragraph as="div"> <p> Worried about remembering all of these class names? The Tailwind CSS IntelliSense extension for VS Code has you covered. </p> <p> Get intelligent autocomplete suggestions, linting, class definitions and more, all within your editor and with no configuration required. </p> </Paragraph> <Link href="/docs/intellisense" color="sky" darkColor="gray"> Learn more<span className="sr-only">, editor setup</span> </Link> </div> <GridLockup className="mt-10" beams={7} left={ <div className="relative"> <img decoding="async" src={require('@/img/beams/overlay.webp').default.src} alt="" className="absolute z-10 bottom-0 -left-80 w-[45.0625rem] pointer-events-none dark:hidden" /> <CodeWindow className="!h-[39.0625rem]"> <div className="flex-auto flex min-h-0"> <div className="hidden sm:flex flex-none w-14 border-r border-slate-500/30 flex-col items-center justify-between pt-3.5 pb-4"> <svg width="24" height="216" fill="none"> <path d="M3 69l6-6m-2-5a7 7 0 1014 0 7 7 0 00-14 0z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M8 7H5a1 1 0 00-1 1v12a1 1 0 001 1h10a1 1 0 001-1v-3m3.707-10.293l-3.414-3.414A1 1 0 0015.586 3H9a1 1 0 00-1 1v12a1 1 0 001 1h10a1 1 0 001-1V7.414a1 1 0 00-.293-.707zM7 103a2 2 0 100-4 2 2 0 000 4zm0 0v10m10-6a2 2 0 100-4 2 2 0 000 4zm0 0a3 3 0 01-3 3h-4a3 3 0 00-3 3m0 0a2 2 0 100 4 2 2 0 000-4z" stroke="currentColor" strokeWidth="1.5" /> <path d="M11.5 160.031a.75.75 0 00-1-1.118l1 1.118zm-8-1.118a.75.75 0 00-1 1.118l1-1.118zm6.972 6.149a.75.75 0 10.993-1.124l-.993 1.124zm-7.937-1.124a.75.75 0 10.993 1.124l-.993-1.124zm7.022-.368l-.64-.393.64.393zm-5.114 0l.64-.393-.64.393zM3 161.25a.75.75 0 000 1.5v-1.5zm8 1.5a.75.75 0 000-1.5v1.5zM8 147l.372-.651A.75.75 0 007.25 147H8zm14 8l.372.651a.75.75 0 000-1.302L22 155zm-14.75 0a.75.75 0 001.5 0h-1.5zm5.378 4.492a.75.75 0 10.744 1.302l-.744-1.302zM7 157.75A2.25 2.25 0 019.25 160h1.5A3.75 3.75 0 007 156.25v1.5zm0-1.5A3.75 3.75 0 003.25 160h1.5A2.25 2.25 0 017 157.75v-1.5zm2.624 3.298A5.225 5.225 0 017 160.25v1.5a6.73 6.73 0 003.376-.903l-.752-1.299zM9.25 160v.197h1.5V160h-1.5zm0 .197V162h1.5v-1.803h-1.5zM7 160.25a5.225 5.225 0 01-2.624-.702l-.752 1.299A6.73 6.73 0 007 161.75v-1.5zM4.75 162v-1.803h-1.5V162h1.5zm0-1.803V160h-1.5v.197h1.5zm5.75-1.284a5.209 5.209 0 01-.876.635l.752 1.299c.403-.234.78-.507 1.124-.816l-1-1.118zm-6.124.635a5.21 5.21 0 01-.876-.635l-1 1.118c.344.309.721.582 1.124.816l.752-1.299zm4.86 4.701c.451.212.867.487 1.236.813l.993-1.124a6.77 6.77 0 00-1.588-1.046l-.64 1.357zM9.25 162c0 .433-.122.835-.332 1.177l1.277.787A3.737 3.737 0 0010.75 162h-1.5zm-.332 1.177A2.247 2.247 0 017 164.25v1.5a3.748 3.748 0 003.195-1.786l-1.277-.787zm-5.39 1.885a5.25 5.25 0 011.235-.813l-.64-1.357a6.77 6.77 0 00-1.588 1.046l.993 1.124zM7 164.25c-.81 0-1.52-.427-1.918-1.073l-1.277.787A3.748 3.748 0 007 165.75v-1.5zm-1.918-1.073A2.235 2.235 0 014.75 162h-1.5c0 .719.203 1.392.555 1.964l1.277-.787zM4 161.25H3v1.5h1v-1.5zm7 0h-1v1.5h1v-1.5zm-3.372-13.599l14 8 .744-1.302-14-8-.744 1.302zM8.75 155v-8h-1.5v8h1.5zm12.878-.651l-9 5.143.744 1.302 9-5.143-.744-1.302z" fill="currentColor" /> <path d="M3 205h8m-8 0v7a1 1 0 001 1h7m-8-8v-7a1 1 0 011-1h6a1 1 0 011 1v7m0 0v8m0-8h7a1 1 0 011 1v6a1 1 0 01-1 1h-7m4-11h6a1 1 0 001-1v-6a1 1 0 00-1-1h-6a1 1 0 00-1 1v6a1 1 0 001 1z" stroke="currentColor" strokeWidth="1.5" /> </svg> <svg width="24" height="72" fill="none"> <path d="M10.325 52.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M15 60a3 3 0 11-6 0 3 3 0 016 0zM5.121 17.804A13.936 13.936 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> </div> <div className="flex-auto flex flex-col min-w-0"> <CompletionDemo /> <div className="border-t border-slate-500/30 font-mono text-xs leading-6 text-slate-200 p-4 space-y-2"> <h3>Problems</h3> <ul className="space-y-1 text-slate-300"> {problems.map((problem, i) => ( <li key={i} className="flex min-w-0"> <svg width="24" height="24" fill="none" className="flex-none text-yellow-400" > <path d="m5.207 16.203 5.072-10.137c.711-1.422 2.736-1.421 3.447 0l5.067 10.137c.642 1.285-.29 2.797-1.723 2.797H6.93c-1.434 0-2.366-1.513-1.723-2.797ZM12 10v2" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M12.5 16a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z" stroke="currentColor" /> </svg> <p className="truncate ml-1">{problem[0]}</p> <p className="hidden sm:block flex-none text-slate-500"> {problem[1]} </p> </li> ))} </ul> </div> </div> </div> </CodeWindow> </div> } /> </section> ) }
ReadyMadeComponents.js
import { IconContainer, Caption, BigText, Paragraph, Link, Widont } from '@/components/home/common' import { useInView } from 'react-intersection-observer' import { motion } from 'framer-motion' import { GridLockup } from '../GridLockup' function AnimatedImage({ animate = false, delay = 0, ...props }) { return ( <motion.img initial={false} animate={animate ? { opacity: 1, y: 0 } : { opacity: 0, y: 24 }} transition={{ duration: 0.5, delay }} alt="" loading="lazy" decoding="async" {...props} /> ) } const w = 1213 const h = 675 const getStyle = (x, y, width) => ({ top: `${(y / h) * 100}%`, left: `${(x / w) * 100}%`, width: `${(width / w) * 100}%`, }) const images = [ { src: require('@/img/tailwindui/0.png').default.src, x: 27, y: 24, width: 236 }, { src: require('@/img/tailwindui/1.png').default.src, x: 287, y: 0, width: 567 }, { src: require('@/img/tailwindui/2.png').default.src, x: 878, y: 47, width: 308 }, { src: require('@/img/tailwindui/3.jpg').default.src, x: 0, y: 289, width: 472 }, { src: require('@/img/tailwindui/4.jpg').default.src, x: 496, y: 289, width: 441 }, { src: require('@/img/tailwindui/5.png').default.src, x: 961, y: 289, width: 252 }, ] export function ReadyMadeComponents() { const { ref: inViewRef, inView } = useInView({ threshold: 0.5, triggerOnce: true }) return ( <section id="ready-made-components"> <div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8"> <IconContainer className="dark:bg-indigo-500 dark:highlight-white/20" light={require('@/img/icons/home/ready-made-components.png').default.src} dark={require('@/img/icons/home/dark/ready-made-components.png').default.src} /> <Caption className="text-indigo-500">Ready-made components</Caption> <BigText> <Widont>Move even faster with Tailwind UI.</Widont> </BigText> <Paragraph> Tailwind UI is a collection of beautiful, fully responsive UI components, designed and developed by us, the creators of Tailwind CSS. It's got hundreds of ready-to-use examples to choose from, and is guaranteed to help you find the perfect starting point for what you want to build. </Paragraph> <Link href="https://tailwindui.com/?ref=landing" color="indigo" darkColor="gray"> Learn more </Link> </div> <GridLockup className="mt-10" beams={0} overhang="lg" leftProps={{ style: { maskImage: 'linear-gradient(to bottom, white, white, transparent)', WebkitMaskImage: 'linear-gradient(to bottom, white, white, transparent)', }, }} left={ <div ref={inViewRef} className="flex justify-center"> <div className="w-[216%] ml-[28%] flex-none sm:w-[76rem] sm:ml-0"> <div className="relative" style={{ paddingTop: `${(h / w) * 100}%` }}> {images.map(({ src, x, y, width }, index) => ( <AnimatedImage key={src} animate={inView} delay={index * 0.2} src={src} className="absolute shadow-xl rounded-lg" style={getStyle(x, y, width)} /> ))} </div> </div> </div> } /> </section> ) }