Skip to content

Commit 90de798

Browse files
authored
feat(web): add AI section to homepage (#3757)
1 parent 4145385 commit 90de798

4 files changed

Lines changed: 341 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ export default function Email() {
4040
};
4141
```
4242

43+
## Using with AI
44+
45+
Install the React Email skill to teach your coding agent (Claude Code, Codex, Cursor, GitHub Copilot, and others) how to build emails with React Email:
46+
47+
```sh
48+
npx skills add resend/react-email
49+
```
50+
51+
The docs are also available for LLMs at [react.email/docs/llms.txt](https://react.email/docs/llms.txt).
52+
4353
## Components
4454

4555
A set of standard components to help you build amazing emails without having to deal with the mess of creating table-based layouts and maintaining archaic markup.

‎apps/web/src/app/page.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Footer } from '@/components/footer';
2+
import AiSection from '@/components/sections/ai';
23
import { EditorHomepage } from '@/components/sections/editor';
34
import HeroSection from '@/components/sections/hero';
45
import IntegrationSection from '@/components/sections/integration';
@@ -13,6 +14,7 @@ const Home = () => (
1314
<HeroSection />
1415
<div className="relative mx-auto flex flex-col justify-between px-2 md:max-w-7xl md:px-4">
1516
<PlaygroundSection />
17+
<AiSection />
1618
<TestimonialSection />
1719
<PatternsSection />
1820
<PrimitivesSection />
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
'use client';
2+
3+
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion';
4+
import { CheckIcon } from 'lucide-react';
5+
import Image from 'next/image';
6+
import Link from 'next/link';
7+
import { type ReactNode, useState } from 'react';
8+
import { Button } from '@/components/button';
9+
import { Code } from '@/components/code';
10+
import { Heading } from '@/components/heading';
11+
import { Text } from '@/components/text';
12+
13+
const AiSection = () => {
14+
const [copiedTitle, setCopiedTitle] = useState<string | null>(null);
15+
16+
const handleCopy = async (item: (typeof items)[number]) => {
17+
try {
18+
await navigator.clipboard.writeText(item.prompt);
19+
} catch {
20+
return;
21+
}
22+
setCopiedTitle(item.title);
23+
window.setTimeout(() => {
24+
setCopiedTitle((current) => (current === item.title ? null : current));
25+
}, 1500);
26+
};
27+
28+
return (
29+
<section className="relative my-24 space-y-12 py-10 text-center max-md:px-6 md:space-y-16">
30+
<Blur />
31+
<div className="relative z-4 max-w-full space-y-4 text-center md:mx-auto md:max-w-160">
32+
<Heading
33+
as="h2"
34+
size="8"
35+
weight="medium"
36+
className="inline-block text-white/80 max-md:mx-auto max-md:max-w-lg md:w-96"
37+
>
38+
Build with any AI
39+
</Heading>
40+
<div className="px-4 md:px-40">
41+
<Text size="5" className="opacity-70">
42+
Describe the email you want. Your agent builds it with React Email.
43+
</Text>
44+
</div>
45+
</div>
46+
<ul className="mx-auto grid w-fit grid-cols-3 gap-6 md:grid-cols-6 lg:gap-16">
47+
{items.map((item) => {
48+
const isCopied = copiedTitle === item.title;
49+
50+
return (
51+
<li
52+
key={item.title}
53+
className="flex flex-col items-center justify-center gap-3"
54+
>
55+
<button
56+
type="button"
57+
onClick={() => {
58+
void handleCopy(item);
59+
}}
60+
className="group flex cursor-pointer flex-col items-center justify-center gap-3 bg-transparent outline-hidden focus-visible:ring-1 focus-visible:ring-slate-7"
61+
>
62+
<Tile>
63+
{isCopied ? (
64+
<CheckIcon className="size-8 text-cyan-11" aria-hidden />
65+
) : (
66+
item.icon
67+
)}
68+
</Tile>
69+
<Text
70+
size="3"
71+
className="relative z-4 grid opacity-90 font-[460] tracking-tight"
72+
aria-live="polite"
73+
>
74+
<span
75+
className={`text-gradient [grid-area:1/1] ${isCopied ? 'invisible' : ''}`}
76+
>
77+
{item.title}
78+
</span>
79+
<span
80+
className={`text-gradient [grid-area:1/1] ${isCopied ? '' : 'invisible'}`}
81+
>
82+
Copied
83+
</span>
84+
</Text>
85+
</button>
86+
</li>
87+
);
88+
})}
89+
</ul>
90+
<div className="relative z-4 mb-0 flex flex-wrap items-center justify-center gap-4">
91+
<Code language="bash" className="w-auto! max-w-full">
92+
npx skills add resend/react-email
93+
</Code>
94+
<Button asChild size="4" appearance="gradient">
95+
<Link href="/docs/llms.txt">Docs for LLMs</Link>
96+
</Button>
97+
</div>
98+
<Image
99+
alt=""
100+
className="pointer-events-none absolute sm:-translate-x-48 -top-20 z-3 scale-110 select-none mix-blend-lighten opacity-100"
101+
fill
102+
src="/static/bg.png"
103+
/>
104+
</section>
105+
);
106+
};
107+
108+
const Tile = ({ children }: { children: ReactNode }) => {
109+
const mouseX = useMotionValue(0);
110+
const mouseY = useMotionValue(0);
111+
const background = useMotionTemplate`radial-gradient(60px circle at ${mouseX}px ${mouseY}px, rgb(187,243,254,0.6), transparent 80%)`;
112+
113+
return (
114+
<div
115+
className="relative rounded-[19px] p-px transition duration-200 ease-in-out group-hover:-translate-y-1"
116+
onMouseMove={(e) => {
117+
const { left, top } = e.currentTarget.getBoundingClientRect();
118+
119+
mouseX.set(e.clientX - left);
120+
mouseY.set(e.clientY - top);
121+
}}
122+
>
123+
<motion.div
124+
className="pointer-events-none absolute inset-0 rounded-[19px] opacity-0 transition duration-300 group-hover:opacity-60"
125+
style={{ background }}
126+
/>
127+
<div className="relative flex items-center w-20 h-20 shrink-0 grow justify-center bg-linear-to-b from-zinc-800 to-zinc-950 rounded-[18px] shadow-[0px_32px_64px_-16px_transparent,0px_16px_32px_-8px_transparent,0px_8px_16px_-4px_transparent,0px_4px_8px_-2px_transparent,0px_-8px_16px_-1px_transparent,0px_2px_4px_-1px_transparent,0px_0px_0px_1px_transparent,inset_0px_0px_0px_1px_rgba(255,255,255,0.1),inset_0px_1px_0px_rgb(255,255,255,0.15)] transition duration-200 ease-in-out group-hover:shadow-[0px_32px_64px_-16px_transparent,0px_16px_32px_-8px_rgba(0,0,0,0.6),0px_8px_16px_-4px_transparent,0px_4px_8px_-2px_transparent,0px_-8px_16px_-1px_transparent,0px_2px_4px_-1px_transparent,0px_0px_0px_1px_transparent,inset_0px_0px_0px_1px_rgba(255,255,255,0.2),inset_0px_1px_0px_rgb(255,255,255,0.25)]">
128+
{children}
129+
</div>
130+
</div>
131+
);
132+
};
133+
134+
const Blur = () => {
135+
return (
136+
<div
137+
aria-hidden="true"
138+
className="pointer-events-none absolute inset-0 z-1"
139+
style={{
140+
background:
141+
'radial-gradient(ellipse 60% 50% at 50% 50%, rgba(37, 99, 235, 0.06) 0%, transparent 70%)',
142+
}}
143+
/>
144+
);
145+
};
146+
147+
const Icons = {
148+
claude: () => (
149+
<svg
150+
width="40"
151+
height="40"
152+
viewBox="0 0 24 24"
153+
xmlns="http://www.w3.org/2000/svg"
154+
aria-hidden
155+
>
156+
<path
157+
fill="#D97757"
158+
d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"
159+
/>
160+
</svg>
161+
),
162+
codex: () => (
163+
<svg
164+
width="40"
165+
height="40"
166+
viewBox="0 0 24 24"
167+
xmlns="http://www.w3.org/2000/svg"
168+
aria-hidden
169+
>
170+
<defs>
171+
<linearGradient
172+
id="codex-gradient"
173+
gradientUnits="userSpaceOnUse"
174+
x1="12"
175+
x2="12"
176+
y1="0"
177+
y2="24"
178+
>
179+
<stop stopColor="#B1A7FF" />
180+
<stop offset=".5" stopColor="#7A9DFF" />
181+
<stop offset="1" stopColor="#3941FF" />
182+
</linearGradient>
183+
</defs>
184+
<path
185+
fill="url(#codex-gradient)"
186+
fillRule="evenodd"
187+
clipRule="evenodd"
188+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
189+
/>
190+
<path
191+
fill="white"
192+
d="M7.282 8.307a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zM12.728 14.547a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
193+
/>
194+
</svg>
195+
),
196+
cursor: () => (
197+
<svg
198+
width="36"
199+
height="41"
200+
viewBox="400 395 166.789 191"
201+
xmlns="http://www.w3.org/2000/svg"
202+
aria-hidden
203+
>
204+
<path
205+
fill="#72716D"
206+
d="M483.395 490.5L566 538.297C565.493 539.178 564.757 539.93 563.845 540.456L486.636 585.13C484.632 586.29 482.159 586.29 480.154 585.13L402.945 540.456C402.034 539.93 401.297 539.178 400.79 538.297L483.395 490.5Z"
207+
/>
208+
<path
209+
fill="#55544F"
210+
d="M483.395 395V490.5L400.79 538.297C400.282 537.416 400 536.398 400 535.346V445.654C400 443.545 401.122 441.6 402.945 440.544L480.15 395.87C481.154 395.29 482.273 395 483.391 395H483.395Z"
211+
/>
212+
<path
213+
fill="#43413C"
214+
d="M565.996 442.703C565.489 441.822 564.752 441.07 563.841 440.544L486.632 395.87C485.632 395.29 484.513 395 483.395 395V490.5L566 538.297C566.507 537.416 566.789 536.398 566.789 535.346V445.654C566.789 444.598 566.511 443.588 566 442.703H565.996Z"
215+
/>
216+
<path
217+
fill="#D6D5D2"
218+
d="M560.218 446.049C560.686 446.858 560.751 447.896 560.218 448.82L485.235 578.974C484.732 579.855 483.392 579.493 483.392 578.479V492.713C483.392 492.029 483.209 491.37 482.877 490.794L560.215 446.045H560.218V446.049Z"
219+
/>
220+
<path
221+
fill="white"
222+
d="M560.218 446.049L482.88 490.797C482.552 490.224 482.073 489.737 481.48 489.394L407.369 446.511C406.49 446.006 406.851 444.663 407.862 444.663H557.824C558.889 444.663 559.754 445.239 560.218 446.049Z"
223+
/>
224+
</svg>
225+
),
226+
copilot: () => (
227+
<svg
228+
width="40"
229+
height="40"
230+
viewBox="0 0 24 24"
231+
xmlns="http://www.w3.org/2000/svg"
232+
aria-hidden
233+
>
234+
<path
235+
fill="white"
236+
fillRule="evenodd"
237+
d="M19.245 5.364c1.322 1.36 1.877 3.216 2.11 5.817.622 0 1.2.135 1.592.654l.73.964c.21.278.323.61.323.955v2.62c0 .339-.173.669-.453.868C20.239 19.602 16.157 21.5 12 21.5c-4.6 0-9.205-2.583-11.547-4.258-.28-.2-.452-.53-.453-.868v-2.62c0-.345.113-.679.321-.956l.73-.963c.392-.517.974-.654 1.593-.654l.029-.297c.25-2.446.81-4.213 2.082-5.52 2.461-2.54 5.71-2.851 7.146-2.864h.198c1.436.013 4.685.323 7.146 2.864zm-7.244 4.328c-.284 0-.613.016-.962.05-.123.447-.305.85-.57 1.108-1.05 1.023-2.316 1.18-2.994 1.18-.638 0-1.306-.13-1.851-.464-.516.165-1.012.403-1.044.996a65.882 65.882 0 00-.063 2.884l-.002.48c-.002.563-.005 1.126-.013 1.69.002.326.204.63.51.765 2.482 1.102 4.83 1.657 6.99 1.657 2.156 0 4.504-.555 6.985-1.657a.854.854 0 00.51-.766c.03-1.682.006-3.372-.076-5.053-.031-.596-.528-.83-1.046-.996-.546.333-1.212.464-1.85.464-.677 0-1.942-.157-2.993-1.18-.266-.258-.447-.661-.57-1.108-.32-.032-.64-.049-.96-.05zm-2.525 4.013c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zm5 0c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zM7.635 5.087c-1.05.102-1.935.438-2.385.906-.975 1.037-.765 3.668-.21 4.224.405.394 1.17.657 1.995.657h.09c.649-.013 1.785-.176 2.73-1.11.435-.41.705-1.433.675-2.47-.03-.834-.27-1.52-.63-1.813-.39-.336-1.275-.482-2.265-.394zm6.465.394c-.36.292-.6.98-.63 1.813-.03 1.037.24 2.06.675 2.47.968.957 2.136 1.104 2.776 1.11h.044c.825 0 1.59-.263 1.995-.657.555-.556.765-3.187-.21-4.224-.45-.468-1.335-.804-2.385-.906-.99-.088-1.875.058-2.265.394zM12 7.615c-.24 0-.525.015-.84.044.03.16.045.336.06.526l-.001.159a2.94 2.94 0 01-.014.25c.225-.022.425-.027.612-.028h.366c.187 0 .387.006.612.028-.015-.146-.015-.277-.015-.409.015-.19.03-.365.06-.526a9.29 9.29 0 00-.84-.044z"
238+
/>
239+
</svg>
240+
),
241+
v0: () => (
242+
<svg
243+
width="44"
244+
height="44"
245+
viewBox="0 0 24 24"
246+
xmlns="http://www.w3.org/2000/svg"
247+
aria-hidden
248+
>
249+
<path
250+
fill="white"
251+
fillRule="evenodd"
252+
clipRule="evenodd"
253+
d="M14.252 8.25h5.624c.088 0 .176.006.26.018l-5.87 5.87a1.889 1.889 0 01-.019-.265V8.25h-2.25v5.623a4.124 4.124 0 004.125 4.125h5.624v-2.25h-5.624c-.09 0-.179-.006-.265-.018l5.874-5.875a1.9 1.9 0 01.02.27v5.623H24v-5.624A4.124 4.124 0 0019.876 6h-5.624v2.25zM0 7.5v.006l7.686 9.788c.924 1.176 2.813.523 2.813-.973V7.5H8.25v6.87L2.856 7.5H0z"
254+
/>
255+
</svg>
256+
),
257+
lovable: () => (
258+
<svg
259+
width="36"
260+
height="36"
261+
viewBox="0 0 24 24"
262+
xmlns="http://www.w3.org/2000/svg"
263+
aria-hidden
264+
>
265+
<defs>
266+
<radialGradient
267+
id="lovable-gradient"
268+
cx="0"
269+
cy="0"
270+
r="1"
271+
gradientTransform="matrix(-1 22.49999 -30.45394 -1.3535 14 3)"
272+
gradientUnits="userSpaceOnUse"
273+
>
274+
<stop offset=".25" stopColor="#FE7B02" />
275+
<stop offset=".433" stopColor="#FE4230" />
276+
<stop offset=".548" stopColor="#FE529A" />
277+
<stop offset=".654" stopColor="#DD67EE" />
278+
<stop offset=".95" stopColor="#4B73FF" />
279+
</radialGradient>
280+
</defs>
281+
<path
282+
fill="url(#lovable-gradient)"
283+
fillRule="evenodd"
284+
clipRule="evenodd"
285+
d="M7.082 0c3.91 0 7.081 3.179 7.081 7.1v2.7h2.357c3.91 0 7.082 3.178 7.082 7.1 0 3.923-3.17 7.1-7.082 7.1H0V7.1C0 3.18 3.17 0 7.082 0z"
286+
/>
287+
</svg>
288+
),
289+
};
290+
291+
const installPrompt = (agent: string) =>
292+
`Install the React Email skill with \`npx skills add resend/react-email --agent ${agent}\`, then help me build an HTML email using React Email components. Docs: https://react.email/docs/llms.txt`;
293+
294+
const items = [
295+
{
296+
title: 'Claude Code',
297+
prompt: installPrompt('claude-code'),
298+
icon: <Icons.claude />,
299+
},
300+
{
301+
title: 'Codex',
302+
prompt: installPrompt('codex'),
303+
icon: <Icons.codex />,
304+
},
305+
{
306+
title: 'Cursor',
307+
prompt: installPrompt('cursor'),
308+
icon: <Icons.cursor />,
309+
},
310+
{
311+
title: 'Copilot',
312+
prompt: installPrompt('github-copilot'),
313+
icon: <Icons.copilot />,
314+
},
315+
{
316+
title: 'v0',
317+
prompt: installPrompt('eve'),
318+
icon: <Icons.v0 />,
319+
},
320+
{
321+
title: 'Lovable',
322+
prompt:
323+
'Import the React Email skill from https://github.com/resend/react-email (path: skills/react-email). Then help me build an HTML email using React Email components. Docs: https://react.email/docs/llms.txt',
324+
icon: <Icons.lovable />,
325+
},
326+
];
327+
328+
export default AiSection;

‎apps/web/src/components/sections/testimonial.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Image from 'next/image';
44

55
const TestimonialSection = () => {
66
return (
7-
<section className="relative z-4 mt-48 bg-background">
7+
<section className="relative z-4 pt-20 pb-10 my-24 bg-background">
88
<div className="relative overflow-hidden rounded-t-3xl border-t border-zinc-800 pt-16 sm:mask-[linear-gradient(to_right,transparent,black_30%,black_70%,transparent)]">
99
<Line />
1010
<Blur />

0 commit comments

Comments
 (0)