A shared system for designers and developers, built around Claude Code.
Valencia is a structured website framework for agency client builds. Whether you're a designer or a developer, the workflow is the same — you direct the work through Claude Code, and Claude handles the implementation. The goal is to get to ~80% complete from a sitemap and lo-fi wireframe before any bespoke work begins.
23
Pre-built blocks
Hero, content, feature, social proof, and conversion — ready to assemble into any page.
4
Section modes
Light, accent, dark, brand — every block adapts automatically through the token system.
~80%
Done from a wireframe
The scaffolding, design system, and page templates are ready. The remaining 20% is where the real craft goes.
Why Valencia exists
Without a structured framework, Claude produces inconsistent output — different patterns in every file, ad-hoc styling decisions, nothing reusable across projects. Valencia gives Claude a single well-defined system to work within: every block follows the same pattern, every token works the same way, every new page uses the right nav and footer pairing. The result is a codebase that stays coherent and predictable whether you're three days in or three months in, and whether the person directing Claude that day is a designer or a developer.
Getting started
Brief Claude, review in the browser, repeat.
The workflow is the same whether you're a designer or a developer — open a Claude Code session in the project and describe what you need. Claude handles the implementation; you handle the direction.
Start a new client project
Ask Claude to fork Valencia for the client. It creates a sibling directory, updates the package name, nav logo, footer, and copyright, and removes the developer tools — leaving a clean project ready for art direction.
Set the colour palette
Share the client's brand colours — hex values, a Figma link, a brand guide, whatever you have. Claude translates them into the token system (--brand, --background, --foreground, etc.) so they propagate across all four section modes automatically.
Set the typeface
Tell Claude the font name. It swaps the existing typeface, wires in the new one via next/font, and updates the CSS variable. If the font isn't on Google Fonts, point Claude to where it lives.
Define the navigation
Describe the site structure — pages, hierarchy, groupings. Claude updates the nav and footer data files to match. There are four dropdown styles available (dropdown, mega, full-width, fullscreen); specify which you want or let Claude choose based on the architecture.
Bring in copy and imagery
Drop in the client's copy and image URLs, or describe the content. Claude replaces the placeholder content across all blocks and flags anything that still needs a real asset.
Assemble the pages
Share a wireframe or describe the page section by section. Claude picks blocks from the library, arranges them, and sets section modes to create the right rhythm and contrast down the page.
Review, give feedback, iterate
Review in the browser. Give feedback in plain terms — "the hero needs more breathing room", "use a dark background on the stats section", "the pricing cards feel too similar to each other". Claude refines from there.
Where the extra time goes
Getting to 80% is fast. The remaining 20% is where the project earns its feel — bespoke animation moments beyond the scroll-reveal system, custom backgrounds, interactions that aren't in the block library, photography art direction. Valencia handles the scaffolding so you and Claude can spend time on the parts that actually differentiate the work.
Blocks
23 pre-built sections.
These are the building blocks Claude assembles client pages from. Each one works out of the box — tell Claude which blocks you want and in what order, and it handles the rest. Preview them all at /blocks.
Hero
hero-primaryhero-secondaryhero-tertiaryContent
logo-marqueelogo-bartext-imagetext-blockimage-gridvideo-sectionFeature
feature-tabsfeature-gridstatstimelineSocial Proof
testimonial-slidertestimonial-gridteam-gridConversion
cta-bannerpricing-tablefaq-accordioncta-primarycta-secondarynewslettercontact-formRoutes
Page templates out of the box.
Each route ships with an appropriate nav variant and footer pairing. Inner pages use minimal nav; marketing pages use mega.
/Documentation
/blocksBlock library preview
/style-guideDesign system QA
/blogBlog landing
/blog/[slug]Article layout
/faqFAQ hub
/privacyLegal template
/not-found404 error page
Adding a new page
Tell Claude you need a new page — give it the page name and what kind it is (marketing, inner, legal, etc.) and Claude will set it up with the right nav and footer pairing, the correct header block, and proper metadata. It follows the same conventions used across every other page in the project.
Tokens & modes
One CSS file, four modes.
All design tokens live in app/globals.css. Update the :root block and every block, every mode, every page propagates the change. The tables below are the reference — knowing the token names lets you direct Claude precisely and catch when something looks off.
Section modes
lightDefault. White bg, near-black text.
accentSlightly grey bg. Subtle contrast.
darkDark bg, light text. High contrast.
brandBrand colour bg. Uses --brand tokens.
Every block accepts a mode prop. Claude sets this based on your direction — "make the stats section dark", "use the brand colour here". All colour changes cascade through the token system; no per-element overrides are needed.
Key semantic tokens
bg-backgroundSection and page backgroundsbg-cardCard and panel surfacesbg-mutedSubtle fills — logo bars, input backgroundsbg-surface-hoverCard / cell hover statetext-foregroundHeadings and primary texttext-muted-foregroundBody copy, labels, secondary texttext-foreground/25Decorative — step numbers, logo namesborder-borderAll borders and dividersUtility classes
.sectionStandard section vertical padding (py-20 lg:py-28).section-smSmall section (py-12 lg:py-16).section-lgLarge section (py-28 lg:py-40).container-siteMax-width wrapper (max-w-7xl, px-6 lg:px-8).eyebrowSection-level label above headings. Text only, no pill..tagCard-level inline chip — "New", "Beta", status labels.btn-primaryPrimary CTA — bg-foreground text-background.btn-secondarySecondary CTA — bordered, transparent.btn-ghostGhost CTA — text only, hover fill.btn-destructiveDestructive action — solid red, delete / cancel flows.btn-linkInline text CTA — underlined, no pill or background.icon / .icon-sm/md/lg/xlIcon font wrapper + size scale (16–48px).card-baseStandard card — border + bg-card + p-8 + rounded-2xl.body-lg/md/smBody copy at three sizes — always text-muted-foregroundBrand tokens (swap per client)
--brandClient primary colour (used as brand-mode background)--brand-surfaceSlightly darker — for cards/cells on a brand background--brand-foregroundText colour on top of the brand backgroundExtending
Adding blocks and pages.
The block library grows over time. When a client needs something that doesn't exist yet, describe it to Claude and it will build it into the library — mode-aware and consistent with every existing block.
Briefing a new block
Describe layout and intent. Claude handles the implementation details.
- 1
"Add a two-column comparison block — left lists what we offer, right lists what competitors don't. Dark background."
- 2
"Three service cards in a row — icon, short heading, two lines of body copy, each links somewhere."
- 3
"Full-width pull quote, centered, author name and title below. Accent mode."
Claude creates the component, adds data to lib/data/blocks.ts, registers it in lib/data/blockLibrary.ts, and adds it to the block showcase. The output follows the same four-step pattern every block in the library uses.
The block pattern (for reference)
Every block follows this structure. Knowing it helps when reviewing Claude's output or giving precise feedback about how a block should behave.
import type { SectionMode } from '@/lib/utils'
import { myBlockData } from '@/lib/data/blocks'
type MyBlockProps = typeof myBlockData & { mode?: SectionMode }
export default function MyBlock({
heading = myBlockData.heading,
body = myBlockData.body,
mode = 'light',
}: Partial<MyBlockProps>) {
return (
<section data-mode={mode} className="section bg-background">
<div className="container-site">
<h2 className="h2 text-foreground">{heading}</h2>
<p className="body-lg">{body}</p>
</div>
</section>
)
}Valencia vs. a client fork
Blocks that could be useful across multiple client projects belong in Valencia. Highly client-specific blocks live only in the fork. When in doubt, build in Valencia — unused blocks cost nothing, and pulling something back in from a fork later is harder than leaving it available.
Constraints
Rules Claude follows — and why.
These are baked into Claude's project instructions, so you don't need to enforce them manually. Understanding them helps you give better direction and recognise when something's gone wrong.
No hardcoded colours in block components
Block components use semantic token classes (text-foreground, bg-background, etc.) — never hardcoded values like text-zinc-900 or bg-white. Hardcoded values are invisible to the mode system, so sections stop responding to light/dark/brand switching. Exceptions: HeroPrimary, CTABanner, and the featured pricing card are intentionally hardcoded dark.
Utility class names are stable
Spacing, typography, and component class names (.btn-primary, .section, .eyebrow, etc.) never change in a client fork — every block depends on them. Changes to the naming convention happen only in Valencia itself and flow downstream.
data-mode on html, not body
Tailwind v4's @theme inline variables resolve in :root scope, which maps to the <html> element. Setting data-mode on <body> breaks the variable chain and mode switching stops working. The ModeSwitcher correctly targets document.documentElement.
No bg/text utilities on the body element
@layer utilities outranks @layer base. Adding bg-white or text-zinc-900 directly to the body in layout.tsx permanently overrides the semantic token classes and breaks mode switching across the entire site.
params is a Promise in Next.js 16
Dynamic route params must be awaited — const { slug } = await params. The old pattern of destructuring directly from the function signature throws at runtime in Next.js 16. Claude handles this correctly; worth knowing when reviewing generated route files.
No @tailwindcss/typography prose classes
The @plugin + Turbopack combination crashes the CSS worker in dev. Use the .rich-text class in globals.css instead — it covers all the same cases and is already defined.
shadcn uses base-ui, not Radix
The UI components in components/ui/ are built on @base-ui/react, not Radix. The APIs differ — Accordion doesn't accept type="single" or collapsibleprops. Claude knows this; useful context if you're reviewing component usage.
External images need allowlisting
All external image hostnames must be declared in next.config.ts under images.remotePatterns. If an image isn't rendering, this is usually why. Tell Claude the source domain and it will add it.
Import GSAP from lib/gsap, never directly
All GSAP and ScrollTrigger imports go through @/lib/gsap — this ensures registerPlugin() is called exactly once. Importing from gsap/ScrollTrigger directly causes the plugin to lose registration between module evaluations. Scroll animations use data-animate attributes handled by ScrollAnimations.tsx — only add use client'to a block if it needs a bespoke animation that can't be driven by the data-attribute system.
What's next
Pending work, in priority order.
Items added here as they're identified. Completed items are removed — this list reflects actual remaining work, not a changelog.
Semantic token system review
As blocks grow more complex (badges, status indicators, interactive elements), the token set may need targeted extensions. Evaluate against actual block needs — avoid adding tokens speculatively.
Mobile QA pass
Systematically review every block at 375px. No mobile audit has been done yet.
Sanity CMS integration
Mock data in lib/data/blocks.ts is shaped to match GROQ returns. Pages become async; block signatures stay the same.
Real form endpoints
ContactForm and Newsletter currently log to console only.
Keeping this document current
This page and CLAUDE.mdare updated together whenever the framework changes — no exceptions. They are the two halves of the same source of truth: CLAUDE.md is Claude's technical reference, this page is the human-readable record. A change to one that isn't reflected in the other is incomplete work.
Concretely: new blocks go in the Block Library section, new routes go in Page Templates, new constraints go in the Rules section, completed roadmap items are removed (not just marked done), and new pending work is added as it's identified. Don't let either document drift.