
Form Cro
- 77 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Form CRO is a Claude skill for optimizing the conversion rate of lead, contact, demo, application, and checkout forms through field-cost analysis, multi-step design, and A/B testing.
About
Form CRO is a Claude skill for conversion-rate optimization of lead-capture, contact, demo-request, application, and checkout forms. It covers field-cost analysis, multi-step form architecture, validation UX, mobile optimization, and structured A/B testing. A developer or marketer uses it to cut form fields, restructure flows, and design tests that raise completion rates.
- Field-cost analysis framework quantifies abandonment per form field
- Multi-step vs single-step decision criteria and validation UX patterns
- A/B test framework and playbooks for lead, contact, demo, and checkout forms
Form Cro by the numbers
- 77 all-time installs (skills.sh)
- Ranked #1,199 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
form-cro capabilities & compatibility
Free; no API keys or external services required.
- Capabilities
- free tool strategy · form cro
- Use cases
- marketing · web design · ui design
- Pricing
- Free
What form-cro says it does
Form optimization for lead capture, contact, demo request, application, and
Every field has a cost measured in abandonment.
For signup/registration flows, use signup-flow-cro.
npx skills add https://github.com/borghei/claude-skills --skill form-croAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 77 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Reduce form abandonment on lead, demo, or checkout forms via field-cost analysis and A/B testing.
Who is it for?
Reducing abandonment on lead-capture, demo-request, or checkout forms and deciding which fields to keep, cut, or enrich later.
Skip if: Signup and registration flows, which the skill defers to signup-flow-cro.
When should I use this skill?
You need to reduce form abandonment, decide single-step vs multi-step, or design a form A/B test.
What you get
A leaner, higher-converting form with justified fields, the right step structure, and a prioritized A/B test plan.
- Field-cost analysis with per-field abandonment estimates
- A/B test plan and form-type field-set recommendations
By the numbers
- Field cost matrix covering ~11 field types
- Recommended field sets across 5 form types
- 6-factor context assessment before optimizing
Files
Form CRO
Production-grade form optimization framework covering field-cost analysis, layout engineering, multi-step form architecture, validation UX patterns, mobile-specific optimization, and structured A/B test design. Applicable to lead capture, contact, demo request, application, survey, and checkout forms. For signup/registration flows, use signup-flow-cro.
---
Table of Contents
- Initial Assessment
- Field-Cost Analysis Framework
- Multi-Step vs Single-Step Decision
- Field Design Patterns
- Validation UX
- CTA and Submit Button Optimization
- Mobile Form Optimization
- Trust and Context Elements
- Form Type Playbooks
- A/B Test Framework
- Metrics and Measurement
- Output Artifacts
- Related Skills
---
Initial Assessment
Required Context
| Question | Why It Matters |
|---|---|
| What type of form? (lead capture, contact, demo, application, checkout) | Different types have different optimal field counts |
| How many fields currently? | Establishes baseline friction level |
| What is the current completion rate? | Benchmark for improvement |
| Where do users abandon? (if field-level analytics exist) | Identifies the specific friction point |
| Mobile vs desktop split? | Mobile forms need separate optimization |
| What happens with submitted data? | Determines which fields are truly necessary |
| Which fields are actually used in follow-up? | Often reveals 30-50% of fields are never used |
| Any compliance requirements? (GDPR, HIPAA) | Constrains what can be removed |
---
Field-Cost Analysis Framework
Every field has a cost measured in abandonment. The question is not "what data would be nice to have" but "what data is worth the conversion loss."
Field Cost Matrix
| Field Type | Estimated Abandonment Cost | Justification Threshold |
|---|---|---|
| Baseline (near zero for gated content) | Always justified for lead forms | |
| First name | +2-3% drop | Justified if personalization drives follow-up |
| Last name | +2-3% drop | Rarely justified for first touch |
| Phone number | +5-10% drop | Only if sales will call within 24 hours |
| Company name | +3-5% drop | Justified for B2B qualification |
| Company size | +3-5% drop | Justified only if routing decisions depend on it |
| Job title | +3-5% drop | Can often be enriched post-submission |
| Industry | +2-3% drop | Can often be enriched post-submission |
| Message/textarea | +5-8% drop | Justified for contact forms, not for lead capture |
| Budget | +8-12% drop | Only justified for high-intent demo/sales forms |
| Custom question | +3-5% per question | Must directly affect lead routing or qualification |
The Enrichment Test
Before including any field, ask: Can this be enriched after submission?
| Field | Enrichable? | Method | Keep in Form? |
|---|---|---|---|
| Company name | Yes (from email domain) | Clearbit, Apollo, manual lookup | Remove |
| Company size | Yes (from company name) | Enrichment API | Remove |
| Industry | Yes (from company name) | Enrichment API | Remove |
| Job title | Partially (from LinkedIn) | Manual enrichment | Remove unless critical for routing |
| Phone number | No | Must be provided | Keep only if sales calls immediately |
| Budget | No | Must be stated | Keep only for high-intent forms |
Recommended Field Sets by Form Type
| Form Type | Minimum Fields | Optimal Fields | Maximum Fields |
|---|---|---|---|
| Newsletter signup | Email + First name | ||
| Content download | Email + First name | Email + Name + Company | |
| Contact form | Email + Message | Email + Name + Message | Email + Name + Subject + Message |
| Demo request | Email + Company | Email + Name + Company + Role | + Phone + Use case + Team size |
| Application form | Varies by requirement | -- | All required fields (justified individually) |
---
Multi-Step vs Single-Step Decision
Decision Criteria
| Factor | Single-Step | Multi-Step |
|---|---|---|
| Total fields | < 5 fields | > 5 fields |
| Field complexity | Simple text inputs | Mix of dropdowns, checkboxes, conditional fields |
| User motivation | Low-commitment (newsletter, content) | High-commitment (demo, application) |
| Qualification need | No routing needed | Different paths based on answers |
| Mobile proportion | < 30% mobile | > 50% mobile |
Multi-Step Best Practices
Step structure:
- Step 1: Easiest fields (email, name) -- lowest friction to start
- Step 2: Qualifying information (company, role, use case)
- Step 3: Specific details (budget, timeline, message)
Progress indication:
- Show progress bar with step count ("Step 2 of 3")
- Show completion percentage
- Label each step with what it covers ("Your Details", "Company Info", "Project Details")
Psychological commitment:
- Once a user completes Step 1, they are 40-60% more likely to complete the form (sunk cost effect)
- Capture the email in Step 1 so you can follow up even if they abandon later
Back navigation:
- Always allow users to go back to previous steps
- Preserve entered data when navigating between steps
- Never reset the form on back navigation
---
Field Design Patterns
Field Labels
| Pattern | When to Use | Example |
|---|---|---|
| Above-field labels | Default for most forms | Label sits above the input |
| Inline labels (floating) | Space-constrained layouts | Label moves from inside to above on focus |
| Left-aligned labels | Wide desktop forms | Label to the left of field |
| Placeholder-only labels | Never | Disappears on input, accessibility failure |
Field Types
| Data Needed | Best Input Type | Avoid |
|---|---|---|
type="email" with validation | Plain text input | |
| Phone | type="tel" with format mask | Plain text input |
| Country | Searchable dropdown | Long static dropdown |
| Company size | Button group (1-10, 11-50, 51-200, 200+) | Free text input |
| Interest/topic | Checkbox group (max 6 options) | Multi-select dropdown |
| Message | Textarea (3-4 rows visible) | Single-line text input |
| Date | Native date picker | Three separate dropdowns |
Conditional Fields
Show additional fields based on earlier answers. This reduces visible complexity while capturing necessary data.
Example: "What is your primary goal?" dropdown shows "Budget range" only if they select "Ready to buy" or "Evaluating solutions."
Rules:
- Conditional fields appear with smooth animation (not instant jump)
- Only 1-2 conditional fields per trigger
- Conditional fields are never required (the trigger answer may change)
---
Validation UX
Real-Time vs Submit-Time Validation
| Validation Type | When to Use |
|---|---|
| Real-time (on blur) | Email format, phone format, required fields |
| On submit | Complex validation, server-side checks |
| Inline suggestions | Company name auto-complete, address lookup |
Error Message Design
| Pattern | Good | Bad |
|---|---|---|
| Position | Below the field, in context | Top of form, disconnected |
| Tone | "Please enter a valid email address" | "Error: Invalid input" |
| Specificity | "Phone must include area code" | "Invalid phone number" |
| Color | Red text + red border on field | Red banner at top of page |
| Icon | Error icon next to message | No visual indicator |
Success Indicators
- Green checkmark on valid fields (especially email and phone)
- Positive microcopy: "Looks good!" on valid email
- Do NOT flash green/red on every keystroke -- validate on blur (when user leaves the field)
---
CTA and Submit Button Optimization
Button Copy Framework
| Form Type | Weak Copy | Strong Copy | Strongest Copy |
|---|---|---|---|
| Content download | Submit | Download Guide | Get My Free Guide |
| Demo request | Submit | Request Demo | Schedule My Demo |
| Contact form | Send | Send Message | Get in Touch |
| Newsletter | Subscribe | Join Newsletter | Get Weekly Tips |
| Free trial | Sign Up | Start Free Trial | Start Building Free |
Rules:
- Use first person ("Get My..." not "Get Your...")
- Specify what they get, not what they do
- Include "Free" when applicable
- Keep under 5 words
Button Design
| Element | Best Practice |
|---|---|
| Color | High contrast against form background, consistent with brand CTA color |
| Size | Full-width on mobile, min 44px height for touch targets |
| Position | Immediately below last field, no gap |
| Loading state | Show spinner + "Sending..." to prevent double-submit |
| Disabled state | Disabled until required fields are valid (with clear visual distinction) |
---
Mobile Form Optimization
Mobile-Specific Rules
| Rule | Implementation |
|---|---|
| Touch targets | Minimum 44x44px for all interactive elements |
| Keyboard types | type="email" for email, type="tel" for phone, type="number" for numeric |
| Auto-focus | Focus first field on page load (with keyboard open) |
| Sticky submit | Pin submit button to bottom of viewport on long forms |
| Input spacing | Minimum 8px between fields to prevent mis-taps |
| Dropdown alternatives | Use button groups or radio buttons instead of dropdowns on mobile |
| Auto-fill | Support browser auto-fill for standard fields (name, email, phone, address) |
Mobile vs Desktop Form Differences
| Aspect | Desktop | Mobile |
|---|---|---|
| Layout | 1 or 2 columns | Always 1 column |
| Field count | Up to 8 | Max 5 per step |
| Dropdown | Standard dropdown | Bottom sheet or full-screen picker |
| Help text | Hover tooltips | Always-visible inline text |
| Validation | On blur | On blur + on submit summary |
---
Trust and Context Elements
Trust Signals Near Forms
| Signal | Placement | Impact |
|---|---|---|
| Privacy assurance | Below submit button | "We'll never share your email" |
| Security badges | Next to form container | SSL, SOC2, GDPR compliance |
| Testimonial | Adjacent to form | Social proof reduces hesitation |
| Response time | Below submit button | "We respond within 2 hours" |
| Subscriber/user count | Above or within form | "Join 10,000+ subscribers" |
Context Reinforcement
| Element | Purpose | Example |
|---|---|---|
| Form header | Remind what they get | "Get your free SEO audit report" |
| Bullet list above form | Reinforce value | "You'll get: Full site analysis, Priority fix list, 30-min review call" |
| Expected next step | Set expectations | "After submitting, we'll email your report within 24 hours" |
---
Form Type Playbooks
Lead Capture (Gated Content)
Goal: Maximize completions while capturing qualified leads.
- Fields: Email only (or Email + First name maximum)
- CTA: Value-specific ("Get My Report", "Download Checklist")
- Trust: "No spam, unsubscribe anytime"
- Post-submit: Immediate download + thank you page + follow-up email
Demo Request
Goal: Capture qualified prospects ready for sales conversation.
- Fields: Email + Name + Company (+ Phone and Team size optional)
- CTA: "Schedule My Demo"
- Trust: "30-minute call, no commitment"
- Post-submit: Calendar booking page or confirmation with scheduling link
- Consider: Embedded calendar (Calendly/Cal.com) instead of form
Contact Form
Goal: Enable communication while routing to correct team.
- Fields: Email + Name + Subject dropdown + Message
- CTA: "Send Message"
- Trust: "We respond within [X] hours"
- Post-submit: Confirmation with expected response time
- Consider: Adding department/topic routing dropdown
Quote Request
Goal: Capture enough detail for accurate quoting.
- Fields: Multi-step form with project details
- CTA: "Get My Quote"
- Trust: "Free quote, no obligation"
- Post-submit: Quote delivery timeline + human follow-up
---
A/B Test Framework
High-Impact Tests (Run First)
| Test | Hypothesis | Success Metric |
|---|---|---|
| Remove phone field | Removing phone increases completion by 5-10% | Completion rate |
| Single-step to multi-step | Multi-step increases completion for 6+ field forms | Completion rate + submission quality |
| CTA copy change | Value-specific copy increases clicks by 10-20% | Click-through rate |
| Add social proof | Testimonial near form increases trust | Completion rate |
Medium-Impact Tests
| Test | Hypothesis | Success Metric |
|---|---|---|
| Field order change | Easiest fields first increases step-1 completion | Step completion rates |
| Inline validation | Real-time feedback reduces form errors | Error rate + completion rate |
| Add progress bar | Visual progress on multi-step increases completion | Completion rate |
| Embedded calendar vs form | Calendar reduces friction for demo requests | Booking rate |
Measurement Rules
- Run each test for minimum 2 weeks or 200 conversions per variant (whichever is longer)
- Track both quantity (completion rate) and quality (lead score, SQL rate)
- A test that increases completions but decreases lead quality is not a win
---
Metrics and Measurement
Key Metrics
| Metric | Formula | Benchmark |
|---|---|---|
| Form completion rate | Submissions / Form views | 20-40% for lead forms, 5-15% for long forms |
| Field-level drop-off | Abandonment per field | Identify the highest-drop field |
| Time to complete | Avg seconds from first interaction to submit | < 60s for simple forms, < 3min for complex |
| Error rate | Users who see error / total users | < 10% |
| Mobile completion rate | Mobile submissions / Mobile form views | Should be within 20% of desktop rate |
Instrumentation Requirements
Track these events in your analytics:
- Form view (impression)
- Form interaction (first field focus)
- Per-field completion (on blur, per field)
- Form submission attempt
- Form submission success
- Form validation error (per field)
---
Output Artifacts
| Artifact | Format | Description |
|---|---|---|
| Form Audit Report | Issue/Impact/Fix/Priority table | Per-field analysis with estimated abandonment cost |
| Recommended Field Set | Justified list | Required vs optional vs enrichable fields with rationale |
| Field Layout Specification | Annotated outline | Order, grouping, label style, validation rules, mobile adaptations |
| CTA Copy Options | 3-option table | Button text variants with reasoning and expected impact |
| A/B Test Plan | Prioritized table | Top 5 tests with hypothesis, variant, metric, and priority |
| Mobile Optimization Checklist | Checkbox list | Mobile-specific fixes with implementation notes |
---
Related Skills
- signup-flow-cro -- Use when the form is an account creation or trial registration flow. Form-cro is for lead capture, contact, and demo forms.
- popup-cro -- Use when the form lives inside a modal or popup. Form-cro handles the form itself; popup-cro handles the trigger, timing, and container.
- page-cro -- Use when the page surrounding the form needs optimization (headline, value prop, layout).
- onboarding-cro -- Use when post-form-submission activation is the bottleneck, not the form itself.
---
Tool Reference
1. form_scorer.py
Purpose: Score a form against CRO best practices across field count, field types, CTA quality, mobile readiness, and trust signals.
python scripts/form_scorer.py form_config.json
python scripts/form_scorer.py form_config.json --json| Flag | Required | Description |
|---|---|---|
form_config.json | Yes | JSON file with form fields, CTA, and context metadata |
--json | No | Output results as JSON |
2. field_cost_analyzer.py
Purpose: Calculate the estimated abandonment cost of each form field and recommend fields to remove, keep, or make enrichable.
python scripts/field_cost_analyzer.py form_fields.json
python scripts/field_cost_analyzer.py form_fields.json --json| Flag | Required | Description |
|---|---|---|
form_fields.json | Yes | JSON file with form fields and their types |
--json | No | Output results as JSON |
--monthly-visitors | No | Monthly form visitors for dollar impact estimate (default: 1000) |
--current-rate | No | Current form completion rate as percentage (default: 25) |
--value-per-lead | No | Dollar value per lead for ROI calculation (default: 50) |
3. ab_test_calculator.py
Purpose: Calculate A/B test sample size, duration, and statistical significance for form optimization experiments.
python scripts/ab_test_calculator.py --baseline 25 --lift 10 --traffic 500
python scripts/ab_test_calculator.py --baseline 25 --lift 10 --traffic 500 --json| Flag | Required | Description |
|---|---|---|
--baseline | Yes | Current conversion rate as percentage (e.g., 25 for 25%) |
--lift | Yes | Minimum detectable lift as percentage (e.g., 10 for 10% relative lift) |
--traffic | Yes | Daily traffic (visitors per day to the form) |
--confidence | No | Confidence level as percentage (default: 95) |
--json | No | Output results as JSON |
---
Troubleshooting
| Problem | Likely Cause | Solution |
|---|---|---|
| Form completion rate below 15% | Too many fields or high-friction fields present | Run field_cost_analyzer.py to identify and remove high-cost fields; target email-only for first-touch lead forms |
| Mobile completion rate 50%+ lower than desktop | Form not optimized for touch input | Ensure 44px touch targets, single-column layout, native keyboard types; replace dropdowns with button groups on mobile |
| Users start but do not finish the form | Friction in middle fields (phone, budget, message) | Move high-friction fields to later steps in a multi-step form; capture email in step 1 |
| High error rate on email or phone fields | Validation too aggressive or unclear error messages | Validate on blur (not keystroke); use specific error copy ("Please include @ in email") not generic ("Invalid input") |
| A/B test results are inconclusive after 4 weeks | Insufficient sample size or too small a lift target | Use ab_test_calculator.py to confirm required sample size; consider testing bigger changes (field removal vs copy tweak) |
| CTA clicks are low despite good page traffic | CTA copy is generic or button is not prominent enough | Replace "Submit" with value-specific copy ("Get My Report"); ensure CTA is full-width on mobile, high-contrast color |
---
Success Criteria
- Form completion rate above 25% for lead capture forms (above 35% is excellent)
- Mobile completion rate within 20% of desktop rate
- Error rate below 10% of form interactions
- Field-level drop-off identifies specific friction points with clear remediation
- Each form field has documented justification (business need or enrichable post-submission)
- A/B tests run for minimum 200 conversions per variant before declaring winner
- Post-form-submission follow-up occurs within 24 hours for demo and contact forms
---
Scope & Limitations
- In scope: Form field optimization, CTA copy, validation UX, mobile optimization, trust signal placement, A/B test design, field cost analysis
- Out of scope: Page-level CRO (use page-cro), popup trigger optimization (use popup-cro), signup/registration flows (use signup-flow-cro), post-submission nurture sequences
- Data dependency: Field-level analytics (per-field drop-off) provide the most actionable data but require analytics instrumentation
- Compliance constraint: GDPR/HIPAA may require specific fields that cannot be removed; document compliance requirements before optimizing
- Statistical validity: A/B test recommendations require sufficient traffic volume; low-traffic forms may need longer test durations or alternative evaluation methods
---
Integration Points
- page-cro -- When the form converts well but the surrounding page does not drive form interactions, optimize the page-level elements first
- popup-cro -- When the form is inside a popup or modal, trigger timing and container design are handled by popup-cro
- signup-flow-cro -- For account creation and registration forms (not lead capture), use signup-flow-cro which handles multi-step auth flows
- onboarding-cro -- When post-form-submission activation is the bottleneck, not the form completion rate itself
- free-tool-strategy -- Free tools often include lead capture forms; use form-cro to optimize the capture form within the tool
#!/usr/bin/env python3
"""
A/B Test Calculator
Calculate A/B test sample size, duration, and statistical significance
for form optimization experiments. Uses standard statistical formulas
for two-proportion z-test.
Usage:
python ab_test_calculator.py --baseline 25 --lift 10 --traffic 500
python ab_test_calculator.py --baseline 25 --lift 10 --traffic 500 --json
"""
import argparse
import json
import math
import sys
def norm_ppf(p: float) -> float:
"""Approximate inverse normal CDF (percent-point function).
Uses Abramowitz and Stegun approximation 26.2.23.
Accurate to ~4.5e-4 for 0 < p < 1.
"""
if p <= 0 or p >= 1:
return 0.0
if p < 0.5:
sign = -1
p = 1 - p
else:
sign = 1
p = p
t = math.sqrt(-2.0 * math.log(1.0 - p))
# Rational approximation
c0 = 2.515517
c1 = 0.802853
c2 = 0.010328
d1 = 1.432788
d2 = 0.189269
d3 = 0.001308
numerator = c0 + c1 * t + c2 * t * t
denominator = 1.0 + d1 * t + d2 * t * t + d3 * t * t * t
result = sign * (t - numerator / denominator)
return result
def calculate_sample_size(baseline_rate: float, min_detectable_lift: float,
confidence: float = 0.95, power: float = 0.80) -> int:
"""Calculate required sample size per variant.
Uses the formula for two-proportion z-test:
n = (Z_alpha/2 + Z_beta)^2 * (p1(1-p1) + p2(1-p2)) / (p2 - p1)^2
"""
p1 = baseline_rate / 100.0
relative_lift = min_detectable_lift / 100.0
p2 = p1 * (1 + relative_lift)
if p2 >= 1.0:
p2 = 0.99
if p1 == p2:
return 0
alpha = 1 - confidence
z_alpha = norm_ppf(1 - alpha / 2)
z_beta = norm_ppf(power)
numerator = (z_alpha + z_beta) ** 2 * (p1 * (1 - p1) + p2 * (1 - p2))
denominator = (p2 - p1) ** 2
if denominator == 0:
return 0
n = math.ceil(numerator / denominator)
return n
def calculate_test(baseline: float, lift: float, daily_traffic: int,
confidence: float) -> dict:
"""Calculate complete A/B test plan."""
sample_per_variant = calculate_sample_size(baseline, lift, confidence / 100.0)
total_sample = sample_per_variant * 2
# Duration calculation
traffic_per_variant = daily_traffic / 2 # Split traffic
if traffic_per_variant > 0:
days_needed = math.ceil(sample_per_variant / traffic_per_variant)
else:
days_needed = float('inf')
# Minimum 14 days for business cycle coverage
days_needed = max(14, days_needed)
weeks_needed = math.ceil(days_needed / 7)
# Expected conversion rates
expected_variant_rate = baseline * (1 + lift / 100.0)
# Conversions needed
min_conversions_per_variant = max(200, math.ceil(sample_per_variant * (baseline / 100.0)))
# Risk assessment
if daily_traffic < 100:
traffic_risk = "HIGH -- low traffic may require 2+ months"
elif daily_traffic < 500:
traffic_risk = "MEDIUM -- adequate for testing major changes"
else:
traffic_risk = "LOW -- sufficient traffic for reliable results"
# Alternative lift scenarios
lift_scenarios = []
for alt_lift in [5, 10, 15, 20, 25, 30]:
alt_sample = calculate_sample_size(baseline, alt_lift, confidence / 100.0)
alt_days = max(14, math.ceil(alt_sample / max(1, traffic_per_variant)))
lift_scenarios.append({
"lift_pct": alt_lift,
"sample_per_variant": alt_sample,
"days_needed": alt_days,
"weeks_needed": math.ceil(alt_days / 7),
})
return {
"inputs": {
"baseline_rate_pct": baseline,
"minimum_detectable_lift_pct": lift,
"daily_traffic": daily_traffic,
"confidence_level_pct": confidence,
"statistical_power_pct": 80,
},
"test_plan": {
"sample_per_variant": sample_per_variant,
"total_sample_needed": total_sample,
"days_needed": days_needed,
"weeks_needed": weeks_needed,
"min_conversions_per_variant": min_conversions_per_variant,
"expected_variant_rate_pct": round(expected_variant_rate, 2),
},
"traffic_assessment": {
"daily_traffic": daily_traffic,
"traffic_per_variant": int(traffic_per_variant),
"risk_level": traffic_risk,
},
"lift_scenarios": lift_scenarios,
"recommendations": _generate_recommendations(days_needed, daily_traffic, lift, baseline),
}
def _generate_recommendations(days: int, traffic: int, lift: float, baseline: float) -> list:
"""Generate test recommendations."""
recs = []
if days > 60:
recs.append({
"priority": "HIGH",
"recommendation": f"Test requires {days} days. Consider testing a bigger change (higher expected lift) to reduce required sample size.",
})
if traffic < 200:
recs.append({
"priority": "HIGH",
"recommendation": "Low daily traffic. Focus on high-impact changes (field removal, CTA rewrite) rather than subtle tweaks.",
})
if lift < 10:
recs.append({
"priority": "MEDIUM",
"recommendation": "Detecting small lifts requires large samples. Test bigger changes or accept a higher minimum detectable lift.",
})
recs.append({
"priority": "MEDIUM",
"recommendation": "Run test for full business cycles (minimum 2 weeks). Avoid ending on weekends or holidays.",
})
recs.append({
"priority": "MEDIUM",
"recommendation": f"Track downstream metrics (lead quality, SQL rate) not just form completion rate. A test that increases quantity but decreases quality is not a win.",
})
return recs
def format_text(result: dict) -> str:
"""Format results as human-readable text."""
lines = []
lines.append("=" * 60)
lines.append("A/B TEST CALCULATOR")
lines.append("=" * 60)
inp = result["inputs"]
lines.append(f"\nBaseline Rate: {inp['baseline_rate_pct']:>8.1f}%")
lines.append(f"Min Detectable Lift:{inp['minimum_detectable_lift_pct']:>8.1f}%")
lines.append(f"Daily Traffic: {inp['daily_traffic']:>8d}")
lines.append(f"Confidence: {inp['confidence_level_pct']:>8.0f}%")
lines.append(f"Power: {inp['statistical_power_pct']:>8d}%")
tp = result["test_plan"]
lines.append(f"\n--- Test Plan ---")
lines.append(f"Sample per Variant: {tp['sample_per_variant']:>8,d}")
lines.append(f"Total Sample Needed: {tp['total_sample_needed']:>8,d}")
lines.append(f"Days Needed: {tp['days_needed']:>8d}")
lines.append(f"Weeks Needed: {tp['weeks_needed']:>8d}")
lines.append(f"Expected Variant Rate:{tp['expected_variant_rate_pct']:>8.2f}%")
ta = result["traffic_assessment"]
lines.append(f"\n--- Traffic Assessment ---")
lines.append(f"Risk Level: {ta['risk_level']}")
lines.append(f"\n--- Lift Scenarios ---")
lines.append(f"{'Lift':>6} {'Sample/Var':>12} {'Days':>6} {'Weeks':>6}")
for s in result["lift_scenarios"]:
lines.append(f"{s['lift_pct']:>5d}% {s['sample_per_variant']:>12,d} {s['days_needed']:>6d} {s['weeks_needed']:>6d}")
lines.append(f"\n--- Recommendations ---")
for r in result["recommendations"]:
lines.append(f"[{r['priority']}] {r['recommendation']}")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Calculate A/B test sample size, duration, and statistical significance."
)
parser.add_argument("--baseline", type=float, required=True,
help="Current conversion rate as percentage (e.g., 25 for 25%%)")
parser.add_argument("--lift", type=float, required=True,
help="Minimum detectable lift as percentage (e.g., 10 for 10%% relative lift)")
parser.add_argument("--traffic", type=int, required=True,
help="Daily traffic (visitors per day)")
parser.add_argument("--confidence", type=float, default=95,
help="Confidence level as percentage (default: 95)")
parser.add_argument("--json", action="store_true", help="Output results as JSON")
args = parser.parse_args()
if args.baseline <= 0 or args.baseline >= 100:
print("Error: --baseline must be between 0 and 100.", file=sys.stderr)
sys.exit(1)
if args.lift <= 0:
print("Error: --lift must be positive.", file=sys.stderr)
sys.exit(1)
if args.traffic <= 0:
print("Error: --traffic must be positive.", file=sys.stderr)
sys.exit(1)
result = calculate_test(args.baseline, args.lift, args.traffic, args.confidence)
if args.json:
print(json.dumps(result, indent=2))
else:
print(format_text(result))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Field Cost Analyzer
Calculate the estimated abandonment cost of each form field and recommend
fields to remove, keep, or enrich post-submission. Includes dollar impact
estimation based on traffic and lead value.
Usage:
python field_cost_analyzer.py form_fields.json
python field_cost_analyzer.py form_fields.json --monthly-visitors 5000 --current-rate 30 --value-per-lead 100
python field_cost_analyzer.py form_fields.json --json
"""
import argparse
import json
import sys
FIELD_COSTS = {
"email": {"drop_pct": 1, "enrichable": False, "category": "essential"},
"first_name": {"drop_pct": 3, "enrichable": False, "category": "standard"},
"last_name": {"drop_pct": 3, "enrichable": False, "category": "standard"},
"full_name": {"drop_pct": 2, "enrichable": False, "category": "standard"},
"phone": {"drop_pct": 8, "enrichable": False, "category": "high_friction"},
"company": {"drop_pct": 4, "enrichable": True, "category": "enrichable"},
"company_size": {"drop_pct": 4, "enrichable": True, "category": "enrichable"},
"job_title": {"drop_pct": 4, "enrichable": True, "category": "enrichable"},
"industry": {"drop_pct": 3, "enrichable": True, "category": "enrichable"},
"message": {"drop_pct": 6, "enrichable": False, "category": "high_friction"},
"textarea": {"drop_pct": 6, "enrichable": False, "category": "high_friction"},
"budget": {"drop_pct": 10, "enrichable": False, "category": "high_friction"},
"website": {"drop_pct": 3, "enrichable": True, "category": "enrichable"},
"country": {"drop_pct": 2, "enrichable": True, "category": "enrichable"},
"dropdown": {"drop_pct": 3, "enrichable": False, "category": "standard"},
"checkbox": {"drop_pct": 1, "enrichable": False, "category": "low_friction"},
"radio": {"drop_pct": 2, "enrichable": False, "category": "low_friction"},
"custom": {"drop_pct": 4, "enrichable": False, "category": "standard"},
}
def analyze_fields(data: dict, monthly_visitors: int, current_rate: float,
value_per_lead: float) -> dict:
"""Analyze form fields for abandonment cost."""
fields = data.get("fields", [])
if not fields:
return {"error": "No field data provided."}
current_leads = monthly_visitors * (current_rate / 100.0)
current_monthly_value = current_leads * value_per_lead
field_results = []
cumulative_drop = 0
for f in fields:
ftype = f.get("type", "custom").lower()
label = f.get("label", ftype)
required = f.get("required", True)
cost_data = FIELD_COSTS.get(ftype, FIELD_COSTS["custom"])
drop_pct = cost_data["drop_pct"]
enrichable = cost_data["enrichable"]
category = cost_data["category"]
if not required:
drop_pct = max(1, drop_pct // 2)
cumulative_drop += drop_pct
# Dollar impact of this field
leads_lost = monthly_visitors * (drop_pct / 100.0)
dollar_impact = leads_lost * value_per_lead
if enrichable and required:
action = "REMOVE -- enrichable post-submission"
elif drop_pct >= 8:
action = "REMOVE unless business-critical"
elif drop_pct >= 5:
action = "REVIEW -- high cost, justify or remove"
else:
action = "KEEP"
field_results.append({
"field": label,
"type": ftype,
"required": required,
"category": category,
"estimated_drop_pct": drop_pct,
"enrichable": enrichable,
"monthly_leads_lost": round(leads_lost, 1),
"monthly_dollar_impact": round(dollar_impact, 2),
"action": action,
})
# Calculate optimized scenario (remove enrichable + high friction optional)
removable_drop = sum(f["estimated_drop_pct"] for f in field_results
if f["action"].startswith("REMOVE"))
optimized_rate = min(95, current_rate + removable_drop * 0.7) # 70% recovery factor
optimized_leads = monthly_visitors * (optimized_rate / 100.0)
optimized_value = optimized_leads * value_per_lead
return {
"summary": {
"total_fields": len(fields),
"required_fields": sum(1 for f in field_results if f["required"]),
"enrichable_fields": sum(1 for f in field_results if f["enrichable"]),
"estimated_cumulative_drop_pct": min(80, cumulative_drop),
},
"current_performance": {
"monthly_visitors": monthly_visitors,
"current_rate_pct": current_rate,
"current_monthly_leads": round(current_leads, 1),
"current_monthly_value": round(current_monthly_value, 2),
},
"optimized_projection": {
"removable_drop_pct": removable_drop,
"projected_rate_pct": round(optimized_rate, 1),
"projected_monthly_leads": round(optimized_leads, 1),
"projected_monthly_value": round(optimized_value, 2),
"monthly_value_gain": round(optimized_value - current_monthly_value, 2),
"annual_value_gain": round((optimized_value - current_monthly_value) * 12, 2),
},
"field_analysis": field_results,
"recommendations": _generate_recommendations(field_results),
}
def _generate_recommendations(fields: list) -> list:
"""Generate recommendations."""
recs = []
removable = [f for f in fields if f["action"].startswith("REMOVE")]
if removable:
names = ", ".join(f["field"] for f in removable[:3])
total_drop = sum(f["estimated_drop_pct"] for f in removable)
recs.append({
"priority": "HIGH",
"recommendation": f"Remove {len(removable)} field(s): {names}. Estimated {total_drop}% drop reduction.",
})
high_cost = [f for f in fields if f["estimated_drop_pct"] >= 8 and not f["action"].startswith("REMOVE")]
if high_cost:
for f in high_cost:
recs.append({
"priority": "MEDIUM",
"recommendation": f"'{f['field']}' has {f['estimated_drop_pct']}% drop cost. Keep only if directly used within 24 hours of submission.",
})
return recs
def format_text(result: dict) -> str:
"""Format results as human-readable text."""
lines = []
lines.append("=" * 70)
lines.append("FIELD COST ANALYZER")
lines.append("=" * 70)
s = result["summary"]
lines.append(f"\nTotal Fields: {s['total_fields']} ({s['required_fields']} required, {s['enrichable_fields']} enrichable)")
lines.append(f"Cumulative Abandonment Estimate: {s['estimated_cumulative_drop_pct']}%")
cp = result["current_performance"]
lines.append(f"\n--- Current Performance ---")
lines.append(f"Monthly Visitors: {cp['monthly_visitors']:,}")
lines.append(f"Current Rate: {cp['current_rate_pct']}%")
lines.append(f"Monthly Leads: {cp['current_monthly_leads']:,.0f}")
lines.append(f"Monthly Value: ${cp['current_monthly_value']:,.2f}")
op = result["optimized_projection"]
lines.append(f"\n--- After Removing Low-Value Fields ---")
lines.append(f"Projected Rate: {op['projected_rate_pct']}%")
lines.append(f"Projected Leads: {op['projected_monthly_leads']:,.0f}")
lines.append(f"Monthly Value Gain: ${op['monthly_value_gain']:,.2f}")
lines.append(f"Annual Value Gain: ${op['annual_value_gain']:,.2f}")
lines.append(f"\n--- Field-by-Field Analysis ---")
lines.append(f"{'Field':<18} {'Type':<12} {'Drop%':>6} {'$/mo Lost':>10} {'Action'}")
for f in result["field_analysis"]:
lines.append(
f"{f['field']:<18} {f['type']:<12} {f['estimated_drop_pct']:>5d}% "
f"${f['monthly_dollar_impact']:>8,.0f} {f['action']}"
)
if result["recommendations"]:
lines.append(f"\n--- Recommendations ---")
for r in result["recommendations"]:
lines.append(f"[{r['priority']}] {r['recommendation']}")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Calculate abandonment cost of each form field."
)
parser.add_argument("input_file", help="JSON file with form fields")
parser.add_argument("--json", action="store_true", help="Output results as JSON")
parser.add_argument("--monthly-visitors", type=int, default=1000,
help="Monthly form visitors (default: 1000)")
parser.add_argument("--current-rate", type=float, default=25,
help="Current completion rate %% (default: 25)")
parser.add_argument("--value-per-lead", type=float, default=50,
help="Dollar value per lead (default: 50)")
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in {args.input_file}: {e}", file=sys.stderr)
sys.exit(1)
result = analyze_fields(data, args.monthly_visitors, args.current_rate,
args.value_per_lead)
if args.json:
print(json.dumps(result, indent=2))
else:
print(format_text(result))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Form Scorer
Score a form against CRO best practices across field count, field types,
CTA quality, mobile readiness, and trust signals.
Usage:
python form_scorer.py form_config.json
python form_scorer.py form_config.json --json
"""
import argparse
import json
import sys
FIELD_TYPE_PENALTIES = {
"email": 0,
"first_name": 2,
"last_name": 3,
"phone": 8,
"company": 4,
"company_size": 4,
"job_title": 4,
"industry": 3,
"message": 6,
"budget": 10,
"custom": 4,
}
ENRICHABLE_FIELDS = ["company", "company_size", "industry", "job_title"]
def score_form(data: dict) -> dict:
"""Score form against CRO best practices."""
form = data.get("form", {})
fields = form.get("fields", [])
cta = form.get("cta", {})
form_type = form.get("type", "lead_capture")
has_mobile_optimization = form.get("mobile_optimized", False)
trust_signals = form.get("trust_signals", [])
is_multi_step = form.get("multi_step", False)
has_validation = form.get("inline_validation", False)
# --- Field Analysis ---
field_count = len(fields)
total_abandonment_cost = 0
field_analysis = []
enrichable_count = 0
for f in fields:
ftype = f.get("type", "custom")
required = f.get("required", True)
penalty = FIELD_TYPE_PENALTIES.get(ftype, 4)
is_enrichable = ftype in ENRICHABLE_FIELDS
if is_enrichable:
enrichable_count += 1
field_analysis.append({
"field": f.get("label", ftype),
"type": ftype,
"required": required,
"estimated_drop_pct": penalty,
"enrichable": is_enrichable,
"recommendation": "Remove (enrichable post-submission)" if is_enrichable and required else
"Keep" if penalty <= 3 else
"Consider removing" if penalty >= 6 else "Review necessity",
})
if required:
total_abandonment_cost += penalty
# --- Scoring Dimensions ---
scores = {}
# Field count score (max 25)
if field_count <= 3:
scores["field_count"] = 25
elif field_count <= 5:
scores["field_count"] = 20
elif field_count <= 7:
scores["field_count"] = 12
else:
scores["field_count"] = max(0, 25 - (field_count - 3) * 3)
# CTA quality (max 20)
cta_score = 0
cta_text = cta.get("text", "Submit")
cta_checks = []
if cta_text.lower() != "submit":
cta_score += 5
cta_checks.append({"check": "Not generic 'Submit'", "passed": True})
else:
cta_checks.append({"check": "Not generic 'Submit'", "passed": False})
if any(w in cta_text.lower() for w in ["my", "your", "free", "get", "start", "download"]):
cta_score += 5
cta_checks.append({"check": "Uses value-oriented language", "passed": True})
else:
cta_checks.append({"check": "Uses value-oriented language", "passed": False})
if len(cta_text.split()) <= 5:
cta_score += 5
cta_checks.append({"check": "Under 5 words", "passed": True})
else:
cta_checks.append({"check": "Under 5 words", "passed": False})
if cta.get("high_contrast", False):
cta_score += 5
cta_checks.append({"check": "High contrast button", "passed": True})
else:
cta_checks.append({"check": "High contrast button", "passed": False})
scores["cta_quality"] = cta_score
# Mobile readiness (max 20)
mobile_score = 0
mobile_checks = []
if has_mobile_optimization:
mobile_score += 8
mobile_checks.append({"check": "Mobile-optimized layout", "passed": True})
else:
mobile_checks.append({"check": "Mobile-optimized layout", "passed": False})
if form.get("single_column", True):
mobile_score += 4
mobile_checks.append({"check": "Single-column layout", "passed": True})
else:
mobile_checks.append({"check": "Single-column layout", "passed": False})
if form.get("keyboard_types", False):
mobile_score += 4
mobile_checks.append({"check": "Correct keyboard types (email, tel)", "passed": True})
else:
mobile_checks.append({"check": "Correct keyboard types (email, tel)", "passed": False})
if form.get("touch_targets_44px", False):
mobile_score += 4
mobile_checks.append({"check": "44px+ touch targets", "passed": True})
else:
mobile_checks.append({"check": "44px+ touch targets", "passed": False})
scores["mobile_readiness"] = mobile_score
# Trust signals (max 15)
trust_score = min(15, len(trust_signals) * 5)
scores["trust_signals"] = trust_score
# Validation UX (max 10)
validation_score = 0
if has_validation:
validation_score += 5
if form.get("error_messages_inline", False):
validation_score += 5
scores["validation_ux"] = validation_score
# Multi-step bonus (max 10)
if is_multi_step and field_count > 5:
scores["multi_step"] = 10
elif not is_multi_step and field_count <= 5:
scores["multi_step"] = 10
elif is_multi_step and field_count <= 5:
scores["multi_step"] = 5 # Unnecessary multi-step
else:
scores["multi_step"] = 0 # Should be multi-step but isn't
total_score = sum(scores.values())
max_score = 100
if total_score >= 80:
grade = "A"
elif total_score >= 65:
grade = "B"
elif total_score >= 50:
grade = "C"
elif total_score >= 35:
grade = "D"
else:
grade = "F"
return {
"form_type": form_type,
"total_score": total_score,
"max_score": max_score,
"grade": grade,
"scores": scores,
"field_analysis": field_analysis,
"field_summary": {
"total_fields": field_count,
"enrichable_fields": enrichable_count,
"estimated_total_abandonment_pct": min(80, total_abandonment_cost),
},
"cta_checks": cta_checks,
"mobile_checks": mobile_checks,
"trust_signals_present": trust_signals,
"recommendations": _generate_recommendations(scores, field_analysis, field_count, is_multi_step),
}
def _generate_recommendations(scores: dict, fields: list, count: int, multi_step: bool) -> list:
"""Generate prioritized recommendations."""
recs = []
if scores["field_count"] < 15:
removable = [f for f in fields if f["enrichable"] and f["required"]]
if removable:
names = ", ".join(f["field"] for f in removable[:3])
recs.append({"priority": "HIGH", "area": "Field count",
"recommendation": f"Remove enrichable fields: {names}. These can be populated post-submission."})
if scores["cta_quality"] < 15:
recs.append({"priority": "HIGH", "area": "CTA",
"recommendation": "Replace generic CTA with value-specific copy (e.g., 'Get My Report' instead of 'Submit')."})
if scores["mobile_readiness"] < 12:
recs.append({"priority": "HIGH", "area": "Mobile",
"recommendation": "Implement mobile optimization: single column, 44px touch targets, correct keyboard types."})
if scores["trust_signals"] < 10:
recs.append({"priority": "MEDIUM", "area": "Trust",
"recommendation": "Add trust signals near the form: privacy assurance, security badges, or testimonial."})
if count > 5 and not multi_step:
recs.append({"priority": "MEDIUM", "area": "Structure",
"recommendation": "Convert to multi-step form. Capture email in step 1, qualifying info in step 2."})
return recs
def format_text(result: dict) -> str:
"""Format results as human-readable text."""
lines = []
lines.append("=" * 60)
lines.append(f"FORM CRO SCORE: {result['grade']} ({result['total_score']}/{result['max_score']})")
lines.append("=" * 60)
lines.append(f"\nForm Type: {result['form_type']}")
lines.append(f"Fields: {result['field_summary']['total_fields']} ({result['field_summary']['enrichable_fields']} enrichable)")
lines.append(f"Est. Abandonment: {result['field_summary']['estimated_total_abandonment_pct']}%")
lines.append(f"\n--- Dimension Scores ---")
for dim, score in result["scores"].items():
lines.append(f" {dim:<20} {score:>3}")
lines.append(f"\n--- Field Analysis ---")
lines.append(f" {'Field':<20} {'Type':<15} {'Drop%':>6} {'Enrichable':>10} {'Action'}")
for f in result["field_analysis"]:
lines.append(f" {f['field']:<20} {f['type']:<15} {f['estimated_drop_pct']:>5d}% {'Yes' if f['enrichable'] else 'No':>10} {f['recommendation']}")
lines.append(f"\n--- CTA Checks ---")
for c in result["cta_checks"]:
status = "[PASS]" if c["passed"] else "[FAIL]"
lines.append(f" {status} {c['check']}")
lines.append(f"\n--- Recommendations ---")
for r in result["recommendations"]:
lines.append(f"[{r['priority']}] {r['area']}: {r['recommendation']}")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Score a form against CRO best practices."
)
parser.add_argument("input_file", help="JSON file with form configuration")
parser.add_argument("--json", action="store_true", help="Output results as JSON")
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in {args.input_file}: {e}", file=sys.stderr)
sys.exit(1)
result = score_form(data)
if args.json:
print(json.dumps(result, indent=2))
else:
print(format_text(result))
if __name__ == "__main__":
main()
Related skills
FAQ
When should a form be multi-step instead of single-step?
Use multi-step when there are more than 5 fields, mixed field complexity, high-commitment intent, qualification routing needs, or over 50% mobile traffic.
Which form fields should be removed?
Fields that can be enriched after submission, like company name, size, and industry from the email domain, should be removed from the form.