Stats & values
Values Grid
A split title, then a tall photo beside four numbered value cards.
Layout and motion from the Kalme template — see it in contextimport { cn } from "@/lib/utils";
/*
* Values Grid — a split title, then a tall photo beside four numbered value cards ("01 /04").
* Source: marcframe/kalme → components/sections/ValuesSection.tsx (+ design-system ValueCard, SectionTitle split).
*
* Layout kept from Framer: the lead sits beside the heading from 768px, aligned to its bottom (up to 400px). Grid,
* 20px gaps: one column below 768px (photo and cards 224px tall); two columns from 768px with the photo across both
* (320px) and cards 320px; three columns from 1024px with the photo down the first column, beside two rows of 355px
* cards. A card: the number and "/<total>" at the top, the title and text at the bottom.
* Nothing moves. The photo is decorative. Dropped: Kalme's red dot in the label.
*/
export type ValueItem = { title: string; text: string };
export type ValuesGridProps = {
/** Small line above the heading. */
label?: string;
title?: string;
lead?: string;
/** Decorative photo, tall from 1024px. */
image?: string;
/** Four reads best; the cards number themselves. */
values?: ValueItem[];
className?: string;
};
const defaults = {
label: "Our values",
title: "What we hold ourselves to",
lead: "Four promises we make to every client — and check ourselves against at the end of every project.",
image: "https://images.unsplash.com/photo-1531403009284-440f080d1e12?auto=format&fit=crop&w=900&q=80",
values: [
{ title: "Clarity first", text: "Plain goals, plain reports. If a number can't be explained in a sentence, we rethink it." },
{ title: "Craft over volume", text: "Fewer, better campaigns — each one made with care from the brief to the last frame." },
{ title: "Data with judgement", text: "We test everything, then decide like people who know the brand, not only the dashboard." },
{ title: "Partners, not vendors", text: "We plan a year ahead with you and stay accountable for what happens next." },
],
};
export function ValuesGrid({
label = defaults.label,
title = defaults.title,
lead = defaults.lead,
image = defaults.image,
values = defaults.values,
className,
}: ValuesGridProps) {
const total = String(values.length).padStart(2, "0");
return (
<section className={cn("py-(--section-y)", className)}>
<div className="mx-auto flex max-w-(--container) flex-col gap-12 px-(--gutter)">
<div className="flex flex-col gap-5 md:flex-row md:items-end md:gap-12 lg:gap-24">
<div className="flex flex-col gap-2.5 md:flex-1">
{label && <p className="text-sm font-medium text-muted-foreground">{label}</p>}
<h2 className="font-heading text-h2 font-semibold tracking-tight text-balance">{title}</h2>
</div>
{lead && <p className="max-w-100 text-pretty text-muted-foreground md:flex-1">{lead}</p>}
</div>
<div className="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
{image && (
<img
src={image}
alt=""
className="h-56 w-full rounded-xl bg-muted object-cover md:col-span-2 md:h-80 lg:col-span-1 lg:row-span-2 lg:h-full"
/>
)}
{values.map((value, i) => (
<div key={value.title} className="flex h-56 flex-col justify-between gap-5 rounded-xl bg-muted p-5 md:h-80 lg:h-88.75 lg:p-7.5">
<p className="flex items-baseline gap-2.5" aria-hidden>
<span className="font-heading text-h3 font-semibold tracking-tight tabular-nums">{String(i + 1).padStart(2, "0")}</span>
<span className="font-heading text-lg font-semibold text-foreground/65 tabular-nums">/{total}</span>
</p>
<div className="flex flex-col gap-2.5">
<h3 className="font-heading text-lg font-semibold tracking-tight">{value.title}</h3>
<p className="text-pretty text-foreground/65">{value.text}</p>
</div>
</div>
))}
</div>
</div>
</section>
);
}Installation
$ npx shadcn@latest add https://framebrick.co/r/blocks/values-grid.json1. Add the block tokens to
app/globals.cssonce (theming).npx shadcn@latest add https://framebrick.co/r/blocks/tokens.json2. Copy the block into
components/values-grid.tsx.import { cn } from "@/lib/utils"; /* * Values Grid — a split title, then a tall photo beside four numbered value cards ("01 /04"). * Source: marcframe/kalme → components/sections/ValuesSection.tsx (+ design-system ValueCard, SectionTitle split). * * Layout kept from Framer: the lead sits beside the heading from 768px, aligned to its bottom (up to 400px). Grid, * 20px gaps: one column below 768px (photo and cards 224px tall); two columns from 768px with the photo across both * (320px) and cards 320px; three columns from 1024px with the photo down the first column, beside two rows of 355px * cards. A card: the number and "/<total>" at the top, the title and text at the bottom. * Nothing moves. The photo is decorative. Dropped: Kalme's red dot in the label. */ export type ValueItem = { title: string; text: string }; export type ValuesGridProps = { /** Small line above the heading. */ label?: string; title?: string; lead?: string; /** Decorative photo, tall from 1024px. */ image?: string; /** Four reads best; the cards number themselves. */ values?: ValueItem[]; className?: string; }; const defaults = { label: "Our values", title: "What we hold ourselves to", lead: "Four promises we make to every client — and check ourselves against at the end of every project.", image: "https://images.unsplash.com/photo-1531403009284-440f080d1e12?auto=format&fit=crop&w=900&q=80", values: [ { title: "Clarity first", text: "Plain goals, plain reports. If a number can't be explained in a sentence, we rethink it." }, { title: "Craft over volume", text: "Fewer, better campaigns — each one made with care from the brief to the last frame." }, { title: "Data with judgement", text: "We test everything, then decide like people who know the brand, not only the dashboard." }, { title: "Partners, not vendors", text: "We plan a year ahead with you and stay accountable for what happens next." }, ], }; export function ValuesGrid({ label = defaults.label, title = defaults.title, lead = defaults.lead, image = defaults.image, values = defaults.values, className, }: ValuesGridProps) { const total = String(values.length).padStart(2, "0"); return ( <section className={cn("py-(--section-y)", className)}> <div className="mx-auto flex max-w-(--container) flex-col gap-12 px-(--gutter)"> <div className="flex flex-col gap-5 md:flex-row md:items-end md:gap-12 lg:gap-24"> <div className="flex flex-col gap-2.5 md:flex-1"> {label && <p className="text-sm font-medium text-muted-foreground">{label}</p>} <h2 className="font-heading text-h2 font-semibold tracking-tight text-balance">{title}</h2> </div> {lead && <p className="max-w-100 text-pretty text-muted-foreground md:flex-1">{lead}</p>} </div> <div className="grid gap-5 md:grid-cols-2 lg:grid-cols-3"> {image && ( <img src={image} alt="" className="h-56 w-full rounded-xl bg-muted object-cover md:col-span-2 md:h-80 lg:col-span-1 lg:row-span-2 lg:h-full" /> )} {values.map((value, i) => ( <div key={value.title} className="flex h-56 flex-col justify-between gap-5 rounded-xl bg-muted p-5 md:h-80 lg:h-88.75 lg:p-7.5"> <p className="flex items-baseline gap-2.5" aria-hidden> <span className="font-heading text-h3 font-semibold tracking-tight tabular-nums">{String(i + 1).padStart(2, "0")}</span> <span className="font-heading text-lg font-semibold text-foreground/65 tabular-nums">/{total}</span> </p> <div className="flex flex-col gap-2.5"> <h3 className="font-heading text-lg font-semibold tracking-tight">{value.title}</h3> <p className="text-pretty text-foreground/65">{value.text}</p> </div> </div> ))} </div> </div> </section> ); }
Usage
import { ValuesGrid } from "@/components/values-grid";
<ValuesGrid
title="What we hold ourselves to"
image="/about/values.jpg"
values={[
{ title: "Clarity first", text: "Plain goals, plain reports." },
{ title: "Craft over volume", text: "Fewer, better campaigns." },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | Our values | Small muted line above the heading. |
| title / lead | string | — | Heading (h2) and the lead beside it from 768px (up to 400px). |
| image | string | — | Decorative photo: across the row from 768px, down the first column from 1024px. |
| values | { title, text }[] | 4 values | Cards numbered 01 /04, 02 /04…; four fills the grid. |
| className | string | — | Classes for the <section>. |
- One column below 768px (photo and cards 224px); two from 768px with the photo across both (320px); three from 1024px with the photo down the first column beside two rows of 355px cards.
- Nothing moves.
AI prompt
Paste into v0, Lovable, Bolt or Claude to rebuild this block in your own project.
Create a React + Tailwind CSS block called ValuesGrid (Next.js). - <section className="py-(--section-y)">, container mx-auto max-w-(--container) px-(--gutter), column gap-12. - Split title: a small muted label and an h2 (font-heading text-h2 semibold); beside it from 768px (items-end) a muted lead up to 400px. - Grid gap-5: 1 / 2 (768px) / 3 (1024px) columns. A photo (rounded-xl, object-cover): 224px tall, across both columns at 320px from 768px, down the first column (row-span-2) from 1024px. Four cards (rounded-xl bg-muted, p-5 / lg:p-7.5; 224 / 320 / 355px tall, justify-between): "01" (font-heading text-h3) and "/04" (text-lg, text-foreground/65) at the top; an h3 title (text-lg semibold) and text (text-foreground/65) at the bottom. - Only shadcn variables and block tokens (secondary text on bg-muted is text-foreground/65: muted-foreground falls under 4.5:1 there); plain <img>.











