Component · Hero sections
Hero Split
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.
Vue 3 call-to-action banner in three layouts (glowing panel, split card and slim band) with primary and secondary buttons. Tailwind CSS, dark mode.
by Vueplayv1.0.0Updated 2 hours ago
Every landing page needs a closing argument. This component gives you three: a dark panel with a soft glow for the end of the page, a split card with text on the left and buttons on the right for mid-page prompts, and a slim band for announcements between sections.
All three share the same props (heading, description, primary and secondary action) so you can switch layouts with a single variant change. Set secondaryAction to null to show just one button, use the heading slot for rich text, and the default slot for extra content like a trust line.
Buttons are regular links that also emit an action event, handy for analytics or opening a signup modal instead of navigating.
<script setup lang="ts">
export interface CtaLink {
label: string
href: string
}
withDefaults(defineProps<{
/**
* 'panel' – centred text in a dark, glowing panel
* 'split' – text left, buttons right, on a light accent panel
* 'band' – slim full-width strip, good between sections
*/
variant?: 'panel' | 'split' | 'band'
heading?: string
description?: string
primaryAction?: CtaLink
secondaryAction?: CtaLink | null
}>(), {
variant: 'panel',
heading: 'Ready to close your books in a day?',
description: 'Start a free 30-day trial of Tallyhouse. Import last year’s data in minutes and see how much time month-end really takes.',
primaryAction: () => ({ label: 'Start free trial', href: '#trial' }),
secondaryAction: () => ({ label: 'Talk to sales', href: '#sales' }),
})
const emit = defineEmits<{
action: [kind: 'primary' | 'secondary', event: MouseEvent]
}>()
const arrow = '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'
</script>
<template>
<!-- Panel -->
<section v-if="variant === 'panel'" class="px-6 py-16 sm:py-20 lg:px-8">
<div class="relative isolate mx-auto max-w-5xl overflow-hidden rounded-3xl bg-gray-900 px-6 py-16 text-center shadow-2xl sm:px-16 sm:py-20 dark:bg-gray-900 dark:ring-1 dark:ring-white/10">
<div aria-hidden="true" class="absolute -top-24 left-1/2 -z-10 h-64 w-[36rem] max-w-none -translate-x-1/2 rounded-full bg-orange-500/40 blur-3xl" />
<div aria-hidden="true" class="absolute inset-0 -z-10 bg-[linear-gradient(to_right,rgb(255_255_255/0.05)_1px,transparent_1px),linear-gradient(to_bottom,rgb(255_255_255/0.05)_1px,transparent_1px)] mask-[radial-gradient(ellipse_at_top,black,transparent_70%)] bg-size-[40px_40px]" />
<h2 class="mx-auto max-w-2xl text-3xl font-bold tracking-tight text-balance text-white sm:text-4xl">
<slot name="heading">{{ heading }}</slot>
</h2>
<p v-if="description" class="mx-auto mt-5 max-w-xl text-lg leading-8 text-pretty text-gray-300">{{ description }}</p>
<div class="mt-10 flex flex-col items-stretch justify-center gap-3 sm:flex-row sm:items-center">
<a :href="primaryAction.href" class="inline-flex items-center justify-center gap-2 rounded-xl bg-orange-500 px-5 py-3 text-sm font-semibold text-gray-950 shadow-sm transition hover:bg-orange-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-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="arrow" 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-white ring-1 ring-white/20 transition ring-inset hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white" @click="emit('action', 'secondary', $event)">
{{ secondaryAction.label }}
</a>
</div>
<slot />
</div>
</section>
<!-- Split -->
<section v-else-if="variant === 'split'" class="px-6 py-16 sm:py-20 lg:px-8">
<div class="mx-auto flex max-w-6xl flex-col gap-8 rounded-3xl bg-orange-50 px-6 py-10 ring-1 ring-orange-900/10 sm:px-10 lg:flex-row lg:items-center lg:justify-between lg:gap-12 lg:px-14 lg:py-14 dark:bg-orange-500/10 dark:ring-orange-400/20">
<div class="max-w-2xl">
<h2 class="text-2xl font-bold tracking-tight text-balance text-gray-900 sm:text-3xl dark:text-white">
<slot name="heading">{{ heading }}</slot>
</h2>
<p v-if="description" class="mt-3 text-base leading-7 text-pretty text-gray-700 dark:text-gray-300">{{ description }}</p>
<slot />
</div>
<div class="flex shrink-0 flex-col gap-3 sm:flex-row">
<a :href="primaryAction.href" class="inline-flex items-center justify-center gap-2 rounded-xl bg-orange-600 px-5 py-3 text-sm font-semibold text-white shadow-sm transition hover:bg-orange-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-600 dark:bg-orange-500 dark:text-gray-950 dark:hover:bg-orange-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="arrow" clip-rule="evenodd" /></svg>
</a>
<a v-if="secondaryAction" :href="secondaryAction.href" class="inline-flex items-center justify-center rounded-xl bg-white px-5 py-3 text-sm font-semibold text-gray-900 ring-1 ring-gray-900/10 transition ring-inset hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-600 dark:bg-gray-900 dark:text-white dark:ring-white/10 dark:hover:bg-gray-800" @click="emit('action', 'secondary', $event)">
{{ secondaryAction.label }}
</a>
</div>
</div>
</section>
<!-- Band -->
<section v-else class="border-y border-orange-800/20 bg-orange-700 dark:border-orange-400/20 dark:bg-orange-500/15">
<div class="mx-auto flex max-w-7xl flex-col gap-4 px-6 py-6 sm:flex-row sm:items-center sm:justify-between lg:px-8">
<p class="text-base font-semibold text-white dark:text-orange-100">
<slot name="heading">{{ heading }}</slot>
<span v-if="description" class="mt-1 block text-sm font-normal text-orange-100 sm:mt-0 sm:ml-2 sm:inline dark:text-orange-200/80">{{ description }}</span>
</p>
<div class="flex shrink-0 items-center gap-4">
<a :href="primaryAction.href" class="inline-flex items-center gap-2 rounded-lg bg-white px-4 py-2 text-sm font-semibold text-orange-800 shadow-sm transition hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-orange-500 dark:text-gray-950 dark:hover:bg-orange-400" @click="emit('action', 'primary', $event)">
{{ primaryAction.label }}
</a>
<a v-if="secondaryAction" :href="secondaryAction.href" class="text-sm font-semibold text-white underline-offset-4 hover:underline focus-visible:rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white dark:text-orange-100" @click="emit('action', 'secondary', $event)">
{{ secondaryAction.label }} <span aria-hidden="true">→</span>
</a>
</div>
</div>
</section>
</template>
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'panel' | 'split' | 'band' | 'panel' | Layout: dark centred panel, split light card, or slim full-width band. |
| heading | string | 'Ready to close your books in a day?' | Main line of the banner. |
| description | string | 'Start a free 30-day trial of Tallyhouse…' | Supporting text. Empty string hides it. |
| primaryAction | CtaLink | { label: 'Start free trial', href: '#trial' } | Main button. |
| secondaryAction | CtaLink | null | { label: 'Talk to sales', href: '#sales' } | Secondary button or link. Pass null to hide it. |
| Event | Payload | Description |
|---|---|---|
| action | (kind: 'primary' | 'secondary', event: MouseEvent) | Fired when either button is clicked. |
| Slot | Description |
|---|---|
| heading | Rich content for the heading. |
| default | Extra content under the text (panel and split variants). |
Component · Hero sections
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.
Component · Feature sections
Alternating Vue 3 feature rows that zigzag text and CSS-drawn illustrations, with bullets and links per row. Tailwind CSS, dark mode, no images needed.
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 · 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.