Radiant:一个漂亮的新营销网站模板

Dan Hollick

我们刚刚完成了一个名为 Radiant 的漂亮的新 SaaS 营销网站模板的工作,它现在作为 Tailwind UI 的一部分提供。

Learn about the Radiant template

它使用 Next.js、Framer Motion 和 Tailwind CSS 构建,博客由 Sanity 驱动。

我们已经有一段时间没有构建像这样的 SaaS 营销模板了,在那段时间里,我们学到了很多关于是什么让这样的模板有用且易于使用的知识。我们已尝试将所有这些经验融入到 Radiant 中。

像往常一样查看 实时预览 以获得完整体验 —— 这一个有很多很酷的细节,您必须在浏览器中看到才能真正欣赏。


富有品味的互动性

在这种类型的网站上,动画很容易做得过火。我们都见过这样的网站,你甚至无法滚动几个像素,就会有一堆不同的元素动画到位。更糟糕的是,当你必须等待内容出现才能阅读时,感觉有多慢。

Radiant 加载了令人愉悦的动画,但它们都叠加在现有内容之上,并由用户交互触发,因此网站仍然感觉很快。在大多数情况下,我们选择了循环动画,使元素在您与之交互时感觉“生动”。

我们几乎所有的动画都使用了 Framer Motion。它是声明式的,可以轻松创建我们自己的 API 来实现复杂的动画,其他人可以轻松自定义这些动画,而无需付出太多努力。

不过,它确实有一些缺点需要解决。例如,当您有多个元素独立动画时,将悬停状态传递给每个子元素是很烦人的。我们最终利用 Framer 的变体传播来解决这个问题 —— 悬停事件触发父元素中的变体更改,该更改会传播到子元素,因为它们共享相同的变体键。

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

父元素中的变体之间没有区别,因此它实际上不会改变,但即使子元素是深度嵌套的,它们仍然会收到在悬停时更改变体的信号。

map.tsx
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>
)
}
/* ... */

徽标时间线动画有点不同,因为我们希望徽标在您停止悬停时暂停在当前位置,而不是返回到其原始位置。这与 Framer 指定开始和结束状态的方法不太协调,因此实际上在 CSS 中构建它更容易。

它利用了您可以设置负 animation-delay 值来偏移元素起始位置的事实。这样,所有徽标都共享相同的动画关键帧,但它们可以从不同的位置开始并具有不同的持续时间。

logo-timeline.tsx
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>
/* ... */

这种方法意味着我们不需要在 JavaScript 中跟踪播放状态,我们只需使用 group-hover:[animation-play-state:running] 类在父元素悬停时启动动画。

正如您可能注意到的,我们在这个组件中为单个 animation 属性使用了一堆任意属性,因为这些实用程序在今天的 Tailwind 中不存在。这就是构建这些模板的伟大之处 —— 它帮助我们找到 Tailwind CSS 中的盲点。谁知道呢,也许我们会在 v4.0 中看到添加这些实用程序!


刻意可重用

设计像这样的 SaaS 模板最棘手的部分是想出交互式元素,人们可以毫不费力地将其应用于自己的产品。没有什么比购买模板后意识到它太特定于示例内容而无法实际用于您自己的项目更糟糕的了。

我们想出了一些大多数 SaaS 产品可能拥有的核心图形元素。带有图钉的地图、徽标集群、键盘 —— 可以应用于许多不同功能的东西。因为我们希望它们易于为您的产品重新利用,所以我们用代码构建了很多,并为它们设计了不错的 API。

例如,徽标集群有一个简单的 API,可让您传入自己的徽标,调整其位置和悬停动画以匹配。

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

键盘快捷键部分是另一个很好的例子。添加您自己的快捷键就像将键名数组传递给 Keyboard 组件一样简单,并且由于每个键都是一个组件,因此您可以轻松添加自定义键或更改布局。

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

事实证明,用代码构建键盘实际上需要做很多工作,但至少现在您永远不必自己发现了。

当然,我们还为您留出了放置您自己产品屏幕截图的位置。以下是使用相同的交互式组件,为适应我们在 SavvyCal 的朋友而定制的这个部分的样子。

Radiant as SavvyCal

由 CMS 驱动

通常,当向模板添加博客时,我们只使用 MDX,但这次我们认为尝试使用无头 CMS 会很有趣。在 调查我们的受众 并听到很多好评后,我们决定尝试一下 Sanity

CMS 让您可以从他们的 UI 处理所有事情,而不是手动创建文件、进行提交和管理图像等内容,因此即使是非开发人员也可以轻松贡献。

Sanity Studio

关于像 Sanity 这样的无头 CMS,一件很酷的事情是您可以以结构化格式取回您的内容,因此类似于 MDX,您可以将元素映射到您自己的自定义组件来处理您的所有排版样式。

<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 || ""} />
),
},
/* ... */
}}
/>

使用 CMS 也意味着您的所有资产(如图像)都为您托管,并且您可以动态控制图像的大小、质量和格式。

<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>
)}

就像您可能在 Markdown 中使用 front matter 一样,您也可以使用自定义字段丰富内容。例如,我们在博客文章架构中添加了一个 featured 布尔字段,以便您可以在博客的特殊部分中突出显示某些帖子。

Radiant Blog

特别是 Sanity 是一款付费产品,但他们有一个非常慷慨的免费层级,这足以让您玩转。如果您想尝试不同的无头 CMS,我认为我们在此处整合的 Sanity 集成仍然可以作为一个很好的信息示例,说明您如何使用另一个工具连接事物。


这就是 Radiant!看看它的内部结构,试用一下,并告诉我们您的想法。

与我们所有的模板一样,它包含在一次性购买的 Tailwind UI 全面访问 许可证中,这是支持我们在 Tailwind CSS 上工作的最佳方式,并使我们能够在未来几年继续为您构建出色的东西。

直接将我们所有的更新发送到您的收件箱。
注册我们的新闻通讯。

版权所有 © 2025 Tailwind Labs Inc.·商标政策