Component · Feature sections
Bento Grid
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.
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.
by Vueplayv1.0.0Updated 2 hours ago
The workhorse section of every product page: a short heading and intro, then a grid of features, each with an icon, a title and one or two sentences of explanation.
It ships with ten matching stroke icons (bolt, shield, chart, sync, users, code, globe, bell, lock, sparkles) drawn as inline SVG, so there is no icon package to install. Need a different one? The scoped icon slot receives each feature and its index.
Pick variant="plain" for an airy, open layout or variant="cards" for bordered tiles, and set columns to 2, 3 or 4. The grid collapses to one column on phones and two on tablets.
<script setup lang="ts">
import { computed } from 'vue'
/** Built-in icon names. Use the `icon` slot for anything else. */
export type FeatureIcon = 'bolt' | 'shield' | 'chart' | 'sync' | 'users' | 'code' | 'globe' | 'bell' | 'lock' | 'sparkles'
export interface Feature {
title: string
description: string
icon?: FeatureIcon
}
const props = withDefaults(defineProps<{
eyebrow?: string
heading?: string
description?: string
features?: Feature[]
/** Columns at the widest breakpoint. */
columns?: 2 | 3 | 4
/** 'plain' puts icons above text; 'cards' wraps each feature in a bordered card. */
variant?: 'plain' | 'cards'
}>(), {
eyebrow: 'Why Ledgerline',
heading: 'Bookkeeping that keeps up with you',
description: 'Everything a small business needs to stay on top of its money, without an accounting degree or a spreadsheet held together with tape.',
columns: 3,
variant: 'plain',
features: () => [
{ icon: 'sync', title: 'Automatic bank feeds', description: 'Connect your accounts once and every transaction lands in Ledgerline within minutes, already categorised.' },
{ icon: 'sparkles', title: 'Smart reconciliation', description: 'Matches receipts to payments for you and flags only the handful that need a human look.' },
{ icon: 'chart', title: 'Live cash-flow forecast', description: 'See the next 90 days of cash at a glance, including upcoming invoices and recurring bills.' },
{ icon: 'bell', title: 'Invoice reminders', description: 'Polite, scheduled nudges go out on your behalf, so you get paid without chasing.' },
{ icon: 'users', title: 'Invite your accountant', description: 'Give your bookkeeper their own login with exactly the access they need. No password sharing.' },
{ icon: 'shield', title: 'Bank-grade security', description: 'Read-only bank connections, encrypted storage and two-factor sign-in on every account.' },
],
})
defineSlots<{
icon?: (props: { feature: Feature, index: number }) => unknown
footer?: () => unknown
}>()
const gridCols = computed(() => ({
2: 'sm:grid-cols-2',
3: 'sm:grid-cols-2 lg:grid-cols-3',
4: 'sm:grid-cols-2 lg:grid-cols-4',
}[props.columns]))
// 24x24 stroke icons, drawn to share one visual weight.
const icons: Record<FeatureIcon, string[]> = {
bolt: ['M13 2 4 14h7l-1 8 9-12h-7l1-8Z'],
shield: ['M12 3 5 6v5c0 4.5 3 8.3 7 10 4-1.7 7-5.5 7-10V6l-7-3Z', 'm9 12 2 2 4-4'],
chart: ['M4 20V10', 'M10 20V4', 'M16 20v-7', 'M22 20H2'],
sync: ['M20 11a8 8 0 0 0-14.3-4.9L4 8', 'M4 3v5h5', 'M4 13a8 8 0 0 0 14.3 4.9L20 16', 'M20 21v-5h-5'],
users: ['M16 20v-1.5a3.5 3.5 0 0 0-3.5-3.5h-5A3.5 3.5 0 0 0 4 18.5V20', 'M10 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z', 'M20 20v-1.5a3.5 3.5 0 0 0-2.5-3.35', 'M15.5 4.15a3.5 3.5 0 0 1 0 6.7'],
code: ['m8 7-5 5 5 5', 'm16 7 5 5-5 5', 'm14 4-4 16'],
globe: ['M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z', 'M3 12h18', 'M12 3c2.5 2.7 3.8 5.7 3.8 9s-1.3 6.3-3.8 9c-2.5-2.7-3.8-5.7-3.8-9S9.5 5.7 12 3Z'],
bell: ['M6 9a6 6 0 1 1 12 0c0 5 2 6.5 2 6.5H4S6 14 6 9Z', 'M10 19a2 2 0 0 0 4 0'],
lock: ['M6 11h12v10H6z', 'M8.5 11V7.5a3.5 3.5 0 0 1 7 0V11'],
sparkles: ['M10 3 11.8 8.2 17 10l-5.2 1.8L10 17l-1.8-5.2L3 10l5.2-1.8L10 3Z', 'M18 14l.9 2.1L21 17l-2.1.9L18 20l-.9-2.1L15 17l2.1-.9L18 14Z'],
}
</script>
<template>
<section class="px-6 py-20 sm:py-24 lg:px-8">
<div class="mx-auto max-w-2xl text-center">
<p v-if="eyebrow" class="text-sm font-semibold text-teal-700 dark:text-teal-400">{{ eyebrow }}</p>
<h2 class="mt-2 text-3xl font-bold tracking-tight text-balance text-gray-900 sm:text-4xl dark:text-white">{{ heading }}</h2>
<p v-if="description" class="mt-4 text-lg text-pretty text-gray-600 dark:text-gray-400">{{ description }}</p>
</div>
<ul class="mx-auto mt-16 grid max-w-6xl grid-cols-1" :class="[gridCols, variant === 'cards' ? 'gap-6' : 'gap-x-10 gap-y-12']">
<li
v-for="(feature, index) in features"
:key="feature.title"
:class="variant === 'cards' ? 'rounded-2xl border border-gray-200 bg-white p-6 transition hover:border-teal-300 hover:shadow-sm dark:border-gray-800 dark:bg-gray-900 dark:hover:border-teal-700' : ''"
>
<div class="grid size-11 place-items-center rounded-xl bg-teal-50 text-teal-700 ring-1 ring-teal-600/15 ring-inset dark:bg-teal-400/10 dark:text-teal-300 dark:ring-teal-400/20">
<slot name="icon" :feature="feature" :index="index">
<svg v-if="feature.icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-5">
<path v-for="d in icons[feature.icon]" :key="d" :d="d" />
</svg>
</slot>
</div>
<h3 class="mt-5 text-base font-semibold text-gray-900 dark:text-white">{{ feature.title }}</h3>
<p class="mt-2 text-sm leading-6 text-gray-600 dark:text-gray-400">{{ feature.description }}</p>
</li>
</ul>
<div v-if="$slots.footer" class="mx-auto mt-16 max-w-6xl text-center">
<slot name="footer" />
</div>
</section>
</template>
| Prop | Type | Default | Description |
|---|---|---|---|
| eyebrow | string | 'Why Ledgerline' | Small accent label above the heading. |
| heading | string | 'Bookkeeping that keeps up with you' | Section heading. |
| description | string | 'Everything a small business needs…' | Intro paragraph. Empty string hides it. |
| features | Feature[] | 6 bookkeeping features | Items with title, description and an optional built-in icon name. |
| columns | 2 | 3 | 4 | 3 | Number of columns at the widest breakpoint. |
| variant | 'plain' | 'cards' | 'plain' | Open layout or bordered cards. |
| Slot | Description |
|---|---|
| icon | Custom icon per feature. Receives { feature, index }. |
| footer | Content centred under the grid, e.g. a link to all features. |
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.
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
"Trusted by" logo cloud for Vue 3 with a bordered grid or an endless marquee, using text wordmarks and SVG marks. Tailwind CSS, dark mode, reduced motion.
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.