Component · Hero sections
Hero Centered
Centered Vue 3 hero with an announcement pill, gradient headline, grid background, two CTAs and a row of wordmarks. Tailwind CSS with dark mode.
Split Vue 3 hero section with copy and two CTAs on the left and a Tailwind-drawn app window mockup on the right. Dark mode, typed props, no images.
by Vueplayv1.0.0Updated 2 hours ago1 installs
The classic SaaS hero: a clear headline with an accent phrase, a short supporting paragraph, a primary and secondary call to action, and a product visual that shows what the app actually looks like.
The product window is built from plain divs and Tailwind utilities rather than a screenshot, so it stays sharp at every size, follows light and dark mode, and weighs nothing. Swap it for your own screenshot or illustration through the visual slot.
Every piece of copy is a prop. Buttons are real links and also emit an action event, so you can route, open a modal or track analytics without forking the component.
<script setup lang="ts">
export interface HeroAction {
label: string
href: string
}
withDefaults(defineProps<{
eyebrow?: string
heading?: string
highlight?: string
description?: string
primaryAction?: HeroAction
secondaryAction?: HeroAction
/** Short reassurance lines shown under the buttons. */
notes?: string[]
/** Name shown in the mocked app window's title bar. */
appName?: string
}>(), {
eyebrow: 'New · Shared inboxes for support teams',
heading: 'Answer every customer',
highlight: 'without the chaos',
description: 'Relaybird pulls email, chat and forms into one calm queue. Assign in a click, see who is replying, and never send the same answer twice.',
primaryAction: () => ({ label: 'Start free trial', href: '#signup' }),
secondaryAction: () => ({ label: 'Book a demo', href: '#demo' }),
notes: () => ['14-day trial', 'No credit card', 'Cancel anytime'],
appName: 'Relaybird',
})
const emit = defineEmits<{
action: [kind: 'primary' | 'secondary', event: MouseEvent]
}>()
const conversations = [
{ initials: 'MA', name: 'Mara Alvik', subject: 'Invoice shows the wrong VAT number', time: '2m', tag: 'Billing', unread: true },
{ initials: 'TO', name: 'Tomas Okafor', subject: 'Can we add a second workspace?', time: '14m', tag: 'Sales', unread: true },
{ initials: 'LK', name: 'Lena Kraus', subject: 'Export to CSV is timing out', time: '1h', tag: 'Bug', unread: false },
{ initials: 'JR', name: 'Jun Reyes', subject: 'Thanks, that fixed it!', time: '3h', tag: 'Done', unread: false },
]
</script>
<template>
<section class="relative overflow-hidden">
<div aria-hidden="true" class="pointer-events-none absolute -top-40 right-0 -z-0 size-[40rem] rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-500/10" />
<div class="relative mx-auto grid max-w-7xl items-center gap-14 px-6 py-20 sm:py-24 lg:grid-cols-2 lg:gap-12 lg:px-8 lg:py-32">
<div class="max-w-xl">
<p v-if="eyebrow" class="inline-flex items-center gap-2 rounded-full bg-indigo-50 px-3 py-1 text-sm font-medium text-indigo-700 ring-1 ring-indigo-600/15 ring-inset dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20">
<span class="size-1.5 rounded-full bg-indigo-600 dark:bg-indigo-400" aria-hidden="true" />
{{ eyebrow }}
</p>
<h1 class="mt-6 text-4xl font-bold tracking-tight text-balance text-gray-900 sm:text-5xl lg:text-6xl dark:text-white">
<slot name="heading">
{{ heading }}
<span v-if="highlight" class="text-indigo-600 dark:text-indigo-400">{{ highlight }}</span>
</slot>
</h1>
<p class="mt-6 text-lg leading-8 text-pretty text-gray-600 dark:text-gray-300">{{ description }}</p>
<div class="mt-10 flex flex-col gap-3 sm:flex-row sm:items-center">
<a
:href="primaryAction.href"
class="inline-flex items-center justify-center gap-2 rounded-xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:hover:bg-indigo-400"
@click="emit('action', 'primary', $event)"
>
{{ primaryAction.label }}
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-4"><path fill-rule="evenodd" d="M3 10a.75.75 0 0 1 .75-.75h10.64l-3.22-3.22a.75.75 0 1 1 1.06-1.06l4.5 4.5a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 1 1-1.06-1.06l3.22-3.22H3.75A.75.75 0 0 1 3 10Z" clip-rule="evenodd" /></svg>
</a>
<a
v-if="secondaryAction"
:href="secondaryAction.href"
class="inline-flex items-center justify-center rounded-xl px-5 py-3 text-sm font-semibold text-gray-900 ring-1 ring-gray-300 transition ring-inset hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:text-white dark:ring-gray-700 dark:hover:bg-gray-800"
@click="emit('action', 'secondary', $event)"
>
{{ secondaryAction.label }}
</a>
</div>
<ul v-if="notes.length" class="mt-8 flex flex-wrap gap-x-6 gap-y-2 text-sm text-gray-500 dark:text-gray-400">
<li v-for="note in notes" :key="note" class="flex items-center gap-2">
<svg viewBox="0 0 20 20" aria-hidden="true" class="size-4 fill-indigo-600 dark:fill-indigo-400"><path fill-rule="evenodd" d="M16.7 5.3a1 1 0 0 1 0 1.4l-8 8a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.4L8 12.6l7.3-7.3a1 1 0 0 1 1.4 0Z" clip-rule="evenodd" /></svg>
{{ note }}
</li>
</ul>
</div>
<!-- Product mock: drawn entirely with divs so it scales and themes with the page. -->
<div class="relative">
<slot name="visual">
<div role="img" :aria-label="`Screenshot of the ${appName} shared inbox`" class="relative rounded-2xl bg-gray-900/5 p-2 ring-1 ring-gray-900/10 dark:bg-white/5 dark:ring-white/10">
<div class="overflow-hidden rounded-xl bg-white shadow-2xl shadow-indigo-900/10 ring-1 ring-gray-900/5 dark:bg-gray-900 dark:shadow-black/40 dark:ring-white/10">
<div class="flex items-center gap-2 border-b border-gray-100 px-4 py-3 dark:border-gray-800">
<span class="size-3 rounded-full bg-rose-400" />
<span class="size-3 rounded-full bg-amber-400" />
<span class="size-3 rounded-full bg-emerald-400" />
<span class="ml-3 hidden rounded-md bg-gray-100 px-3 py-1 text-xs text-gray-500 sm:block dark:bg-gray-800 dark:text-gray-400">app.{{ appName.toLowerCase() }}.example/inbox</span>
</div>
<div class="grid grid-cols-[3.5rem_1fr] sm:grid-cols-[9rem_1fr]">
<div class="space-y-1 border-r border-gray-100 p-2 sm:p-3 dark:border-gray-800">
<div class="mb-3 flex items-center gap-2 px-1">
<span class="grid size-7 place-items-center rounded-lg bg-indigo-600 text-xs font-bold text-white">{{ appName.charAt(0) }}</span>
<span class="hidden text-sm font-semibold text-gray-900 sm:block dark:text-white">{{ appName }}</span>
</div>
<div v-for="(item, i) in ['Inbox', 'Mine', 'Snoozed', 'Closed']" :key="item" class="flex items-center gap-2 rounded-md px-2 py-1.5 text-xs" :class="i === 0 ? 'bg-indigo-50 font-medium text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300' : 'text-gray-500 dark:text-gray-400'">
<span class="size-2 shrink-0 rounded-sm" :class="i === 0 ? 'bg-indigo-500' : 'bg-gray-300 dark:bg-gray-600'" />
<span class="hidden sm:inline">{{ item }}</span>
<span v-if="i === 0" class="ml-auto hidden rounded bg-indigo-600 px-1.5 text-[10px] text-white sm:inline">12</span>
</div>
</div>
<ul class="divide-y divide-gray-100 dark:divide-gray-800">
<li v-for="c in conversations" :key="c.name" class="flex items-start gap-3 px-3 py-3 sm:px-4" :class="c.unread ? '' : 'opacity-70'">
<span class="grid size-8 shrink-0 place-items-center rounded-full bg-gray-100 text-[11px] font-semibold text-gray-700 dark:bg-gray-800 dark:text-gray-200">{{ c.initials }}</span>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
<span class="truncate text-xs font-semibold text-gray-900 dark:text-white">{{ c.name }}</span>
<span v-if="c.unread" class="size-1.5 rounded-full bg-indigo-500" />
<span class="ml-auto text-[11px] text-gray-400">{{ c.time }}</span>
</div>
<p class="mt-0.5 truncate text-xs text-gray-500 dark:text-gray-400">{{ c.subject }}</p>
</div>
<span class="hidden rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-600 sm:inline dark:bg-gray-800 dark:text-gray-300">{{ c.tag }}</span>
</li>
</ul>
</div>
</div>
<!-- Floating toast -->
<div class="absolute -bottom-10 left-3 flex items-center sm:-bottom-6 gap-3 rounded-xl bg-white px-4 py-3 shadow-lg ring-1 ring-gray-900/5 sm:-left-8 dark:bg-gray-800 dark:ring-white/10">
<span class="grid size-8 place-items-center rounded-full bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-4"><path fill-rule="evenodd" d="M16.7 5.3a1 1 0 0 1 0 1.4l-8 8a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.4L8 12.6l7.3-7.3a1 1 0 0 1 1.4 0Z" clip-rule="evenodd" /></svg>
</span>
<div>
<p class="text-xs font-semibold text-gray-900 dark:text-white">Median first reply</p>
<p class="text-xs text-gray-500 dark:text-gray-400">4 min · down 38% this week</p>
</div>
</div>
</div>
</slot>
</div>
</div>
</section>
</template>
| Prop | Type | Default | Description |
|---|---|---|---|
| eyebrow | string | 'New · Shared inboxes for support teams' | Small pill above the heading. Pass an empty string to hide it. |
| heading | string | 'Answer every customer' | Main headline. |
| highlight | string | 'without the chaos' | Accent-coloured phrase appended to the heading. |
| description | string | 'Relaybird pulls email, chat and forms…' | Supporting paragraph under the heading. |
| primaryAction | HeroAction | { label: 'Start free trial', href: '#signup' } | Primary button label and link. |
| secondaryAction | HeroAction | { label: 'Book a demo', href: '#demo' } | Secondary button label and link. |
| notes | string[] | ['14-day trial', 'No credit card', 'Cancel anytime'] | Short reassurance lines with check marks under the buttons. |
| appName | string | 'Relaybird' | Product name shown inside the mocked app window. |
| Event | Payload | Description |
|---|---|---|
| action | (kind: 'primary' | 'secondary', event: MouseEvent) | Fired when either CTA is clicked. Call event.preventDefault() to handle navigation yourself. |
| Slot | Description |
|---|---|
| heading | Replaces the heading and highlight text inside the h1. |
| visual | Replaces the drawn app window on the right, e.g. with a real screenshot. |
Component · Hero sections
Centered Vue 3 hero with an announcement pill, gradient headline, grid background, two CTAs and a row of wordmarks. Tailwind CSS with dark mode.
Component · Hero sections
Vue 3 waitlist hero with an accessible email form: inline validation, loading and success states, and a typed submit event. Tailwind CSS, dark mode.
Component · Feature sections
Responsive Vue 3 feature grid with a heading, inline SVG icons and plain or card variants in 2, 3 or 4 columns. Tailwind CSS, dark mode, typed data.
Component · Feature sections
Bento-style Vue 3 feature grid with wide and tall tiles and small CSS-drawn visuals like maps, timelines and code. Tailwind CSS, dark mode, typed tiles.