Block Contact — React Page Block
Contact section: a direct-channels list (email, phone, office — real links) beside a configurable form skeleton with labels wired to inputs and native validation intact.

Usage
import { BlockContact } from "@/components/BlockContact";
export function Reach() {
return (
<BlockContact
title="Talk to a human."
channels={[{ label: "Email", value: "hello@example.com", href: "mailto:hello@example.com" }]}
fields={[{ name: "message", label: "What are you building?", type: "textarea", required: true }]}
onSubmit={async (values) => console.log(values)}
{...{
title: "Talk to a human.",
submitLabel: "Send message",
accent: "#d5ed9a",
scheme: "dark"
}}
/>
);
}Implementation notes & API
Purpose: contact section with channels and a form. Copy it, ship it — every string, link and color arrives through props. Runtime: pure DOM + CSS. No WebGPU, canvas, animation library or external assets; SSR-safe on first render. Theming: root custom properties (--vb-bg, --vb-fg, --vb-accent, --vb-border, --vb-radius, --vb-container) or the accent/scheme/className/style props. scheme flips the dark default to light. Guardrails: responsive via container queries down to 320px; interactive behavior degrades on touch; prefers-reduced-motion drops entrance/scroll motion while content stays complete. Default channels use reserved fictional contact details visibly marked as demo; without onSubmit nothing is sent anywhere.
| Property | Type | Notes / default |
|---|---|---|
eyebrow / title / description | ReactNode | Section header |
channels | readonly ContactChannel[] | { label, value, href?, icon? } — real links you relink freely |
fields | readonly ContactField[] | { name, label, type?, required?, placeholder? } |
onSubmit | (values: Record<string, string>) => void | Promise<void> | Your handler; absent = local demo mode |
demoNote | ReactNode | null | Demo-details marker; pass null once real |
scheme | "dark" | "light" | dark |
accent | string (hex) | Sets --vb-accent for the whole block |
className / style | string / CSSProperties | Applied to the section root |
children | ReactNode | Replaces the default content entirely |