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

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Secure Thoughts
美团技术团队
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
月光博客
月光博客
T
Tor Project blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
I
Intezer
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
GbyAI
GbyAI
罗磊的独立博客
V
V2EX
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
A
About on SuperTechFans
Scott Helme
Scott Helme
Vercel News
Vercel News
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
C
CERT Recently Published Vulnerability Notes
G
Google Developers Blog
B
Blog
博客园 - 叶小钗
WordPress大学
WordPress大学
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
IT之家
IT之家
C
Cybersecurity and Infrastructure Security Agency CISA
P
Palo Alto Networks Blog
小众软件
小众软件
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
AWS News Blog
AWS News Blog

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.