Website blocks
VUI is two halves. The admin half generates screens from data. This half builds the public site in front of it: 60 marketing blocks in @viliha/vui-web, on the same tokens, composable by an agent.
What are website blocks?
A block is one section of a marketing page, taking all of its content as props. A hero, a feature grid, a pricing table, an FAQ. You compose a page by stacking them, and you never write the markup for a section yourself. If a page needs a section that is not a block, that is a missing block rather than a reason to hand-roll one.
They are MIT licensed and always will be, like everything else already published. They live in a separate package because a marketing site does not need the datatable, and an admin app does not need a cookie banner.
pnpm add @viliha/vui-web
# The theme comes from the same file the admin app uses.
# app/globals.css
@import "@viliha/vui-ui/theme.css";How do I build a page?
Stack blocks in the order the page should read. Here is a landing page in full. There is no bespoke markup in it, which is the test the library has to pass.
import { Cta, Faq, FeatureGrid, Hero, LogoCloud, Pricing, Stats } from "@viliha/vui-web";
export default function HomePage() {
return (
<>
<Hero
variant="product"
eyebrow="Now with Vue"
title="The admin app your team will enjoy using"
lead="Datatables, forms, charts and auth screens that already match each other."
actions={<a href="/pricing/">Start free</a>}
visual={<img src="/screenshot.png" alt="" />}
/>
<LogoCloud title="Used by teams at" items={LOGOS} variant="marquee" />
<FeatureGrid title="An admin app, not a box of parts" items={FEATURES} />
<Stats items={STATS} tone="muted" />
<Pricing plans={PLANS} showToggle yearlyNote="Save 20%" />
<Faq title="Before you ask" items={FAQS} defaultOpen={0} />
<Cta title="Build your admin app this week" actions={<a href="/pricing/">Start free</a>} />
</>
);
}Content lives beside the page, not inside it
lib/content.ts and let the page read as a list of sections. Changing a headline then never means opening a component, and an agent editing that one file changes the whole site without touching JSX.What blocks are there?
Around 60, in eight groups. The full list with props is in the package README, and an agent can ask for it directly (see below).
How does an agent build my site?
The MCP server that ships inside @viliha/vui-ui gained three tools for this. An agent asks the installed package what to build with, rather than guessing from documentation that is a year older than your version.
claude mcp add vui -- npx -y @viliha/vui-ui mcpThe recipes behind compose_page are filtered against the blocks actually installed, so it can never recommend one that was renamed away.
How do I style a block?
You do not. Blocks read the same tokens as the admin app, so a brand change repaints both. What you choose per block is its tone (the full-bleed background: default, muted, card, brand) and its width (the content column). Passing a className that sets a colour, a font size or a margin is the thing that makes a site drift.
<FeatureGrid items={FEATURES} />
<Stats items={STATS} tone="muted" />
<Testimonials items={QUOTES} />
<Pricing plans={PLANS} tone="muted" />Decoration
A few classes in theme.css handle the effects a marketing page wants and an admin page never did: .vui-aurora for a brand wash behind a hero, .vui-grid-bg and .vui-dot-bg for a faint texture, .vui-glow for a spotlight, .vui-gradient-text for a gradient headline, .vui-lift for a card that answers the pointer, and .vui-reveal for a section that fades in as it arrives. All of them are mixed from --button-primary, so they follow the tenant’s brand and none of them carries a colour of its own.
Do the forms work?
Newsletter and ContactForm submit nowhere until you wire them, and they say so rather than pretending. A static export has no server to post to. Give them an action for a form endpoint, or an onSubmit to call your own API. The eight field states, the validation and the success and error messages are already written either way.
<ContactForm action="https://formspree.io/f/xxxx" method="post" />
// or
<ContactForm onSubmit={async (data) => { await fetch("/api/contact", { method: "POST", body: JSON.stringify(data) }); }} />Where is the reference site?
apps/website in the repository is 65 pages built entirely from these blocks: home, pricing, features and their detail pages, solutions, services, customers, careers, blog, guides, resources, events, webinars, news, press and the legal pages. It is a static export, so it deploys to any static host. Copy its lib/site.ts and lib/catalog.ts to start your own.
One shape for every detail page
Entry type and one pair of components rendering all of them, rather than sixteen templates that drift apart within a month.