惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
MyScale Blog
MyScale Blog
Microsoft Security Blog
Microsoft Security Blog
量子位
B
Blog
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
The GitHub Blog
The GitHub Blog
T
The Exploit Database - CXSecurity.com
N
News | PayPal Newsroom
Cloudbric
Cloudbric
A
About on SuperTechFans
AI
AI
Hacker News: Ask HN
Hacker News: Ask HN
S
Schneier on Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
T
The Blog of Author Tim Ferriss
Simon Willison's Weblog
Simon Willison's Weblog
有赞技术团队
有赞技术团队
H
Heimdal Security Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
D
Docker
Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
IT之家
IT之家
Know Your Adversary
Know Your Adversary
N
Netflix TechBlog - Medium
T
Tailwind CSS Blog
B
Blog RSS Feed
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
博客园 - 叶小钗
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
The Hacker News
The Hacker News
Y
Y Combinator Blog
I
Intezer
The Register - Security
The Register - Security
F
Full Disclosure
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler

Tailwind CSS Blog

Tailwind CSS v4.3: Scrollbars, new colors, and more Vanilla JavaScript support for Tailwind Plus Compass: A starter kit for online courses Tailwind CSS v4.1: Text shadows, masks, and tons more Tailwind UI is now Tailwind Plus Tailwind CSS v4.0 Tailwind CSS v4.0 Beta 1 Headless UI v2.1: Simplified transition API and improved multi-dialog support Automatically clean up whitespace and duplicate class names Catalyst: Application layouts, navigation menus, description lists, and more Headless UI v2.0 for React We're hiring a Design Engineer + Staff Engineer Open-sourcing our progress on Tailwind CSS v4.0 Introducing Catalyst: A modern UI kit for React Tailwind CSS v3.4: Dynamic viewport units, :has() support, balanced headlines, subgrid, and more Heroicons Micro: What are these, icons for ants? Meet Studio: Our beautiful new agency site template Tailwind Connect 2023: Recap of our first in-person event New changelog template + the biggest Tailwind UI update ever Tailwind CSS v3.3: Extended color palette, ESM/TS support, logical properties, and more Protocol: A beautiful starting point for your next API documentation site Tailwind CSS v3.2: Dynamic breakpoints, multi-config, and container queries, oh my! We built you a new personal website + Heroicons v2.0, Headless UI v1.7, and more New Tailwind CSS + Framer Motion template and Tailwind Jobs Tailwind UI: Site templates and all-access Tailwind CSS v3.1: You wanna get nuts? Come on, let's get nuts! Headless UI v1.6, Tailwind UI team management, Tailwind Play improvements, and more Headless UI v1.5: The One With Comboboxes Automatic Class Sorting with Prettier Effortless Typography, Even in Dark Mode Standalone CLI: Use Tailwind CSS without Node.js Tailwind CSS v3.0 Introducing Tailwind UI Ecommerce Headless UI v1.4: The One With Tabs Tailwind CSS v2.2 Tailwind UI: Now with React + Vue support Headless UI v1.0 Tailwind CSS v2.1 Heroicons v1.0 Just-In-Time: The Next Generation of Tailwind CSS Welcoming James McDonald to Tailwind Labs "Tailwind CSS: From Zero to Production" on YouTube Welcoming David Luhr to Tailwind Labs Multi-line truncation with @tailwindcss/line-clamp Tailwind CSS v2.0 Tailwind CSS v1.9.0 Introducing Tailwind Play Headless UI: Unstyled, Accessible UI Components "What's new in Tailwind CSS?" on YouTube Tailwind CSS v1.8.0 Utility-Friendly Transitions with @tailwindui/react Introducing Heroicons.com Tailwind CSS v1.7.0 From Nine Hundred to One: How We Hired Robin Malfait Tailwind CSS v1.6.0 Simon Vrachliotis Joins Tailwind Labs Welcoming Brad Cornes to the Team Tailwind CSS v1.5.0 Introducing Tailwind CSS Typography Building the Tailwind Blog with Next.js Introducing linting for Tailwind CSS IntelliSense
Radiant: A beautiful new marketing site template
2024-09-12 · via Tailwind CSS Blog

We just wrapped up work on a beautiful new SaaS marketing site template called Radiant, and it's available now as part of Tailwind UI.

It’s built with Next.js, Framer Motion, and Tailwind CSS, with a blog powered by Sanity.

It's been a while since we built a SaaS marketing template like this, and in that time we've learned a lot about what makes a template like this useful and easy to work with. We've tried to incorporate all of those learnings into Radiant.

Check out the live preview as always for the full experience — there are tons of cool details in this one that you have to see in the browser to really appreciate.


Tastefully interactive

It's super easy to overdo animation on a site like this. We've all seen sites where you can't even scroll a few pixels without a bunch of different elements animating in to place. Even worse is how slow things feel when you have to wait for the content to appear before you can read it.

Radiant is loaded with delightful animations, but they are all layered on to existing content and triggered by user interaction so the site still feels fast. In most cases, we went for animations that loop to make elements feel "alive" while you're interacting with them.

We used Framer Motion for almost all of the animations. It's declarative, making it easy to create our own APIs for complex animations that other people can customize without much effort.

It does have some drawbacks to work around though. For example, when you have multiple elements animating independently it's annoying to pass a hover state down to each child. We ended up leveraging Framer's variant propagation to make this work — a hover event triggers a variant change in the parent that propagates down to the children because they share the same variant keys.

export function BentoCard() {  return (    <motion.div      initial="idle"      whileHover="active"      variants={{ idle: {}, active: {} }}      data-dark={dark ? "true" : undefined}    >      /* ... */    </motion.div>  );}

There is no difference between the variants in the parent so it doesn't actually change but the children still get a signal to change variants on hover, even if they are deeply nested.

function Marker({  src,  top,  offset,  delay,}: {  src: string  top: number  offset: number  delay: number}) {  return (    <motion.div      variants={{        idle: { scale: 0, opacity: 0, rotateX: 0, rotate: 0, y: 0 },        active: { y: [-20, 0, 4, 0], scale: [0.75, 1], opacity: [0, 1] },      }}      transition={{ duration: 0.25, delay, ease: 'easeOut' }}      style={{ '--offset': `${offset}px`, top } as React.CSSProperties}      className="absolute left-[calc(50%+var(--offset))] size-[38px] drop-shadow-[0_3px_1px_rgba(0,0,0,.15)]"    >      /* ... */    </motion.div>  )}/* ... */

The logo timeline animation is a bit different, because we wanted the logos to pause in their current position when you stop hovering, rather than return to their original position. This doesn't play very well with Framer's approach of specifying start and end states, so it was actually easier to build this in CSS.

It exploits the fact that you can set a negative animation-delay value to offset the start position of the element. That way all the logos share the same animation keyframes but they can start at different positions and have different durations.

function Logo({  label,  src,  className,}: {  label: string  src: string  className: string}) {  return (    <div      className={clsx(        className,        'absolute top-2 grid grid-cols-[1rem,1fr] items-center gap-2 whitespace-nowrap px-3 py-1',        'rounded-full bg-gradient-to-t from-gray-800 from-50% to-gray-700 ring-1 ring-inset ring-white/10',        '[--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] [animation-iteration-count:infinite] [animation-name:move-x] [animation-play-state:paused] [animation-timing-function:linear] group-hover:[animation-play-state:running]',      )}    >      <img alt="" src={src} className="size-4" />      <span className="text-sm/6 font-medium text-white">{label}</span>    </div>  )}export function LogoTimeline() {  return (    /* ... */    <Row>      <Logo        label="Loom"        src="./logo-timeline/loom.svg"        className="[animation-delay:-26s] [animation-duration:30s]"      />      <Logo        label="Gmail"        src="./logo-timeline/gmail.svg"        className="[animation-delay:-8s] [animation-duration:30s]"      />    </Row>    /* ... */

This approach means we don't need to track the play state in JavaScript, we can just use a group-hover:[animation-play-state:running] class to start the animation when the parent is hovered.

As you've maybe noticed, we're using a bunch of arbitrary properties for individual animation properties in this component, since these utilities don't exist in Tailwind today. This is what's great about building these templates — it helps us find blind spots in Tailwind CSS. Who knows, maybe we'll see these utilities added for v4.0!


Deliberately reusable

The trickiest part of designing a SaaS template like this, is coming up with interactive elements that people can apply to their own product without too much effort. There's nothing worse than buying a template and realizing that it's so specific to the example content that you can't actually use it for your own project.

We came up with some core graphical elements that most SaaS products might have. A map with pins, a logo cluster, a keyboard — things that could be applied to a bunch of different features. Because we wanted them to be easy to repurpose for your own product, we built a lot of them in code and designed nice APIs for them.

The logo cluster, for example, has a simple API that lets you pass in your own logos, tweak their position and hover animation to match.

<Logo src="./logo-cluster/dribbble.svg" left={285} top={20} hover={{ x: 4, y: -5, rotate: 6, delay: 0.3 }} />

The keyboard shortcuts section is another good example. Adding your own shortcuts is as simple as passing an array of key names to the Keyboard component and because each key is a component, you can easily add custom keys or change the layout.

<Keyboard highlighted={["F", "M", "L"]} />

It turns out it's actually quite a lot of work to build a keyboard in code, but at least now you'll never have to find that out for yourself.

Of course, we also left spots for you to drop in screenshots of your own product. Here's what this section looks like customized to suit our friends at SavvyCal, using the same interactive components.


Powered by a CMS

Usually we just use MDX when adding a blog to a template, but this time we thought it would be fun to play with a headless CMS for a chance instead. We decided to give Sanity a go for this one after polling our audience and hearing a lot of good things.

Instead of creating files, making commits, and managing images and stuff by hand, a CMS lets you handle everything from their UI, so even non-developers can easily contribute.

One cool thing about headless CMSes like Sanity is you get your content back in a structured format, so similar to MDX you can map elements to your own custom components to handle all of your typography styles.

<PortableText  value={post.body}  components={{    block: {      normal: ({ children }) => <p className="my-10 text-base/8 first:mt-0 last:mb-0">{children}</p>,      h2: ({ children }) => (        <h2 className="mt-12 mb-10 text-2xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">          {children}        </h2>      ),      h3: ({ children }) => (        <h3 className="mt-12 mb-10 text-xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">          {children}        </h3>      ),      blockquote: ({ children }) => (        <blockquote className="my-10 border-l-2 border-l-gray-300 pl-6 text-base/8 text-gray-950 first:mt-0 last:mb-0">          {children}        </blockquote>      ),    },    types: {      image: ({ value }) => (        <img className="w-full rounded-2xl" src={image(value).width(2000).url()} alt={value.alt || ""} />      ),    },    /* ... */  }}/>

Working with a CMS also means all of your assets like images are hosted for you, and you can control the size, quality, and format of the image on the fly.

<div className="text-sm/5 max-sm:text-gray-700 sm:font-medium">  {dayjs(post.publishedAt).format('dddd, MMMM D, YYYY')}</div>{post.author && (  <div className="mt-2.5 flex items-center gap-3">    {post.author.image && (      <img        className="aspect-square size-6 rounded-full object-cover"        src={image(post.author.image).width(64).height(64).url()}        alt=""      />    )}    <div className="text-sm/5 text-gray-700">      {post.author.name}    </div>  </div>)}

Like you might do with front matter in Markdown, you can also enrich content with custom fields. For example, we added a featured boolean field to the blog post schema so you can highlight some posts in a special section on the blog.

Sanity in particular is a paid product, but they have a pretty generous free tier which is more than enough to play around. And if you wanted to try out a different headless CMS, I think the Sanity integration we've put together here will still serve as a great informative example of how you might approach wiring things up with another tool.


And that's Radiant! Have a look under the hood, kick the tires, and let us know what you think.

Like all of our templates, it's included with a one-time purchase Tailwind UI all-access license, which is the best way to support our work on Tailwind CSS and make it possible for us to keep building awesome stuff for you for years to come.