Gathering requirements from clients for web projects is messy. Long email chains, missing information, repeated questions, and unclear expectations. Every project starts with friction instead of clarity.
Brief is a multi-step, adaptive form that collects everything you need to build a client's website — from business goals to visual identity to technical requirements — in one clean, guided session. When the client hits "Send", you receive a beautifully formatted email with the complete brief attached as a .md file, ready for your workflow.
The form adapts its questions based on client type. A filmmaker sees portfolio and video content steps. A business sees services and credibility sections. No irrelevant questions.
personal → 10 steps (services-focused)
creative → 11 steps (portfolio + content)
business → 11 steps (services + portfolio)
agency → 11 steps (services + portfolio)
other → 9 steps (essentials only)
Send each client a unique URL that greets them by name and pre-selects their category:
https://formulario-webs.vercel.app?client=Jeremy&type=creative
https://formulario-webs.vercel.app?client=Aydile&type=business
https://formulario-webs.vercel.app?client=Gio&type=agency
When the client submits, you receive:
- HTML email — Styled brief with color-coded sections, dark header card, and clean typography
- Markdown attachment —
.mdfile with the complete brief, ready for AI-assisted development workflows
Form data saves to localStorage on every step change. If the client closes the browser and returns later, they pick up exactly where they left off.
Required fields (business name, email, client type) are validated before advancing. Inline error messages guide the client without blocking their flow.
aria-pressedon toggle buttonsaria-progressbaron progress indicatorfocus-visibleoutlines for keyboard navigationprefers-reduced-motionsupport- Minimum 44px touch targets
┌─────────────────────────────────────────────────┐
│ CLIENT BROWSER │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Form.jsx │→ │config.js │→ │ localStorage │ │
│ │ (UI + │ │ (shared │ │ (persistence)│ │
│ │ steps) │ │ config) │ │ │ │
│ └────┬─────┘ └──────────┘ └──────────────┘ │
│ │ │
│ │ POST /api/send-brief │
└───────┼──────────────────────────────────────────┘
│
▼
┌───────────────────┐ ┌─────────────┐
│ Vercel Serverless │ ──→ │ Resend │ ──→ Email
│ api/send-brief.js │ │ (SMTP) │ to you
└───────────────────┘ └─────────────┘
.
├── api/
│ └── send-brief.js # Vercel serverless function (Resend integration)
├── public/
│ └── favicon.svg # "B" logo favicon
├── src/
│ ├── config.js # Shared constants, brief sections, markdown generator
│ ├── Form.jsx # All UI components: primitives, steps, summary, app
│ └── main.jsx # React entry point
├── docs/
│ └── plans/ # Implementation plans
├── index.html # Entry HTML with fonts, global styles, a11y
├── vercel.json # Vercel deployment config
├── vite.config.js # Vite build config
└── package.json
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React 18 | Component UI with hooks |
| Build | Vite 6 | Fast dev server and optimized builds |
| Backend | Vercel Serverless Functions | API endpoint for email sending |
| Resend | Transactional email delivery | |
| Hosting | Vercel | Edge deployment with automatic SSL |
| Styling | Inline CSS-in-JS | Apple-inspired design system |
git clone https://github.com/Oscarmp7/Formulario_Webs.git
cd Formulario_Webs
npm installCreate a .env file in the project root:
RESEND_API_KEY=re_your_api_key_hereIn api/send-brief.js, change the recipient email:
to: ["[email protected]"],# Frontend only (no email sending)
npm run dev
# Full stack with serverless functions
npx vercel dev# Link to Vercel
npx vercel link
# Add environment variable
echo "re_your_key" | npx vercel env add RESEND_API_KEY production
# Deploy
npx vercel --prodEach step collects a specific category of information:
| # | Step | ID | Collects |
|---|---|---|---|
| 1 | Perfil | tipo |
Client type selection (personal, creative, business, agency, other) |
| 2 | Negocio | negocio |
Business name, tagline, contact info, language, current URL |
| 3 | Objetivos | objetivos |
Goals, primary CTA, success metrics, competitors |
| 4 | Audiencia | audiencia |
Target types (B2B/B2C), ideal client, markets, desired feeling |
| 5 | Marca | identidad |
Existing assets, colors, fonts, visual references |
| 6 | Contenido | contenido |
Video platform, hero video, portfolio categories (creative only) |
| 7 | Trabajos | portfolio |
Case studies, project elements, filters (creative/business/agency) |
| 8 | Servicios | servicios |
Service list, presentation style, credibility sections (not creative/other) |
| 9 | Historia | about |
Statement, bios, stats, awards, values |
| 10 | Diseno | ux |
Visual style, theme, animations, sections, impact level |
| 11 | Tecnico | tecnico |
Domain, hosting, integrations, special requirements |
| 12 | Entrega | entrega |
Launch date, content deadline, approver, revision cycles |
Form.jsx
│
├── Primitives
│ ├── SectionCard Card wrapper with title/subtitle
│ ├── FieldGroup Grouped fields with label separator
│ ├── Field Label + note + children wrapper
│ ├── TextInput Input/textarea with focus states
│ ├── Chips Single/multi-select toggle buttons
│ └── Sep Visual separator line
│
├── Step Components (12)
│ ├── StepTipo Client type selection
│ ├── StepNegocio Business information
│ ├── StepObjetivos Goals and objectives
│ ├── StepAudiencia Target audience
│ ├── StepIdentidad Visual identity
│ ├── StepContenido Audio/video content
│ ├── StepPortfolio Portfolio/case studies
│ ├── StepServicios Services listing
│ ├── StepAbout Company story
│ ├── StepUX Design direction
│ ├── StepTecnico Technical requirements
│ └── StepEntrega Delivery/timeline
│
├── StepWrapper Fade animation on step transitions
├── Summary Review all collected data
├── ThankYou Post-submission confirmation
└── App Main controller (state, nav, validation, send)
1. Client opens form
↓
2. URL params read → pre-fill clientType + clientName
↓
3. localStorage checked → restore previous session (if any)
↓
4. Client fills steps → data saved to state + localStorage on each change
↓
5. Validation runs on "Next" → blocks if required fields missing
↓
6. Summary screen → client reviews all data
↓
7. "Enviar brief" clicked
↓
8. POST /api/send-brief { data, markdown }
↓
9. Serverless function:
├── Generates HTML email (color-coded sections)
├── Creates .md attachment from markdown string
└── Sends via Resend API
↓
10. Success → ThankYou screen + localStorage cleared
Error → Error message + retry button
Sends the completed brief via email.
Request Body:
{
"data": {
"clientType": "creative",
"businessName": "Studio XYZ",
"email": "[email protected]",
"tagline": "Visual storytelling",
...
},
"markdown": "# Brief — Studio XYZ\n**Tipo**: Creativo...\n\n## PERFIL\n..."
}Required fields in data: businessName, email, clientType
Responses:
| Status | Body | Meaning |
|---|---|---|
200 |
{ "success": true } |
Email sent successfully |
400 |
{ "error": "Missing required fields..." } |
Validation failed |
405 |
{ "error": "Method not allowed" } |
Not a POST request |
500 |
{ "error": "Failed to send email" } |
Resend API error |
Single source of truth for all form configuration:
| Export | Type | Purpose |
|---|---|---|
CLIENT_TYPES |
Array |
Client type options with id, label, description, icon |
STEPS_CONFIG |
Object |
Step sequence per client type |
STEP_LABELS |
Object |
Display labels for each step |
STEP_ICONS |
Object |
Emoji icons for each step |
getBriefSections(data) |
Function |
Returns structured sections array from form data |
generateMarkdown(data) |
Function |
Generates markdown string from form data |
Defined in REQUIRED_FIELDS inside Form.jsx:
const REQUIRED_FIELDS = {
tipo: { clientType: "Selecciona un tipo de proyecto" },
negocio: { businessName: "El nombre es obligatorio",
email: "El email es obligatorio" },
};To add validation to other steps, add the step key with field-message pairs.
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY |
Yes | Resend API key (starts with re_) |
Edit api/send-brief.js line:
to: ["[email protected]"],Edit api/send-brief.js line:
from: "Your Brand <[email protected]>",Note: To use a custom sender domain (e.g.,
[email protected]), add and verify your domain in the Resend dashboard.
- Create the step component in
Form.jsxfollowing existing patterns - Add it to
STEP_COMPSmap - Add its key to
STEPS_CONFIGfor relevant client types - Add label in
STEP_LABELSand icon inSTEP_ICONS(inconfig.js) - Add any new fields to
getBriefSections()andgenerateMarkdown()
Add the step key to REQUIRED_FIELDS in Form.jsx:
const REQUIRED_FIELDS = {
tipo: { clientType: "Selecciona un tipo de proyecto" },
negocio: { businessName: "El nombre es obligatorio", email: "El email es obligatorio" },
// Add more:
entrega: { launchDate: "La fecha de lanzamiento es obligatoria" },
};| Token | Hex | Usage |
|---|---|---|
| Primary Text | #1d1d1f |
Headings, body text |
| Secondary Text | #8e8e93 |
Labels, notes, muted text |
| Placeholder | #adadb8 |
Input placeholders |
| Blue Accent | #007AFF |
CTAs, focus states, active pills |
| Purple Accent | #5856D6 |
Gradient endpoints |
| Green Success | #34C759 |
Completed states, success |
| Red Error | #FF3B30 |
Validation errors |
| Background | #f2f2f7 |
Page background |
| Card | #ffffff |
Card backgrounds |
| Input BG | #f8f8fa |
Input resting state |
| Border | #e8e8ed |
Input borders, separators |
| Element | Size | Weight | Font |
|---|---|---|---|
| Card Title | 24px | 700 | Inter |
| Field Label | 13px | 600 | Inter |
| Field Note | 12px | 400 | Inter |
| Input Text | 14px | 400 | Inter |
| Chip | 13px | 450/600 | Inter |
| Step Pill | 11px | 400-600 | Inter |
| Group Label | 10px | 700 | Inter (uppercase) |
| Element | Value |
|---|---|
| Card padding | 28px 24px |
| Card radius | 20px |
| Input radius | 12px |
| Chip radius | 24px |
| Button radius | 14px |
| Field gap | 8px |
| Section gap | 16-18px |
| Max content width | 560px |
Private project. All rights reserved.
Built with precision by Oscar