FaceFlow Components

Reusable business blocks with schema-driven fields, Facet templates, scoped styles, and optional runtime behavior.

Component Developer Docs

Components are schema-driven website sections rendered through FaceFlow.

For technical users, Components are the core reusable section contract in the system. They combine field definitions, Facet template output, optional styling, optional client-side behavior, and scope-aware reuse.

Core Responsibility

A Component is responsible for:

  • defining a reusable section contract
  • exposing a controlled authoring schema
  • rendering output through Facet
  • optionally attaching scoped styles and behavior
  • supporting reuse at page, layout, or site level

Components should solve repeatable section problems. They should not become loose containers for arbitrary page-specific markup.

Component Definition Model

A typical Component definition includes:

{
  "name": "contact-section",
  "title": "Contact Section",
  "version": "1.0.0",
  "type": "component",
  "category": "contact",
  "defaultScope": "page",
  "description": "Contact section with CTA and embedded form support",
  "fields": [
    { "name": "title", "label": "Title", "type": "text" },
    { "name": "summary", "label": "Summary", "type": "textarea" },
    { "name": "contactForm", "label": "Form", "type": "formSelect" }
  ],
  "html": "<section><h2>{{ title }}</h2><p>{{ summary }}</p><div data-form-embed=\"{contactForm}\"></div></section>",
  "style": ".contact-section { padding: 4rem 0; }",
  "script": "/* optional behavior */"
}

This model is what allows a Component to act like a governed section contract instead of a loose fragment of copied code.

Core Metadata

Important metadata fields include:

  • name
  • title
  • version
  • type
  • category
  • defaultScope
  • description

Guidance:

  • keep name stable after release
  • use title for editor clarity
  • update version intentionally when schema or render behavior changes
  • choose defaultScope based on expected reuse, not convenience

Field Schema

The field schema defines what authors can change without altering the section structure.

Supported top-level field types include:

  • text
  • textarea
  • number
  • url
  • date
  • image
  • images
  • file
  • select
  • checkbox
  • richtext
  • htmlcode
  • map
  • repeater
  • formSelect
  • reviewSelect

Additional subfield types can be used inside repeater, including:

  • color
  • email
  • tel
  • range

Example:

{
  "name": "faqItems",
  "label": "FAQ Items",
  "type": "repeater",
  "fields": [
    { "name": "question", "type": "text" },
    { "name": "answer", "type": "textarea" }
  ]
}

Choose field types by content intent, not by convenience. A url should stay a URL. A repeated card set should be a repeater, not several unrelated text fields.

See the full field reference:

Template Model

The html portion of a Component is rendered through Facet.

That means templates can use:

  • field output
  • conditionals
  • loops
  • filters
  • page-aware context
  • reusable Variables

Example:

<section class="hero-section">
  <div class="container">
    <h1>{{ title }}</h1>

    {{#if summary}}
      <p>{{ summary }}</p>
    {{/if}}

    {{#if ctaUrl}}
      <a href="{{ ctaUrl }}" class="btn-primary">{{ ctaLabel }}</a>
    {{/if}}
  </div>
</section>

Or with a repeater:

<div class="faq-list">
  {{#each faqItems as="item"}}
    <article class="faq-item">
      <h3>{{ item.question }}</h3>
      <p>{{ item.answer }}</p>
    </article>
  {{/each}}
</div>

For syntax details:

Scope Model

Components can exist at three reuse levels:

Conceptually:

site scope   -> reusable across the broader site
layout scope -> shared inside one page family
page scope   -> local to one page

Scope determines both visibility and change impact. If a Component changes at site scope, that is a broad architectural change, not a local edit.

For the broader model:

Rendering Behavior

At render time, FaceFlow resolves the Component instance by combining:

  • the Component definition
  • saved field values
  • current rendering context
  • related runtime services such as media, Variables, Forms, and Reviews

Conceptually:

component definition
  + saved field values
  + runtime context
  -> Facet render
  -> final section output

This is the point where field data becomes runtime markup.

Styles and Behavior

Components may attach optional styles and optional behavior.

Example:

.pricing-card {
  border-radius: 1rem;
  padding: 2rem;
}
document.querySelectorAll('.faq-item button').forEach((button) => {
  button.addEventListener('click', () => {
    button.closest('.faq-item').classList.toggle('is-open');
  });
});

Use this sparingly. Most section behavior should remain simple, predictable, and easy to maintain.

Forms and Reviews Inside Components

Two field types are especially important for interactive sections:

  • formSelect
  • reviewSelect

These allow Components to embed business workflows without hard-coding one specific model.

Typical markers:

<div data-form-embed="{contactForm}"></div>
<div data-review-embed="{serviceReview}"></div>

This keeps the section reusable while allowing the selected Form or Review model to change at authoring time.

Example Component Patterns

Basic content section:

fields:
  title
  summary
template:
  heading + paragraph

FAQ section:

fields:
  faqItems repeater
template:
  each item -> question + answer

Lead capture section:

fields:
  title
  summary
  contactForm (formSelect)
template:
  copy + data-form-embed marker

Technical Review Checklist

  • does the Component solve one repeatable section problem?
  • is the field schema clear and intentional?
  • is the template readable and stable?
  • is the chosen scope correct for the expected reuse?
  • would a future editor understand how to use it from the field contract alone?

Anti-Patterns

Avoid:

  • overloading one Component with unrelated use cases
  • adding page-specific business logic that should stay outside the section contract
  • using htmlcode when a structured field model would be safer
  • widening scope just because the section might be reused someday
  • renaming fields after content is already stored against them

When To Split a Component

Split a Component when:

  • the field schema has become too broad
  • several layouts or page contexts need conflicting versions
  • the template is full of exceptions and one-off branches
  • editors no longer understand what the section is supposed to do

If the contract stops being clear, reuse usually stops being valuable.