Jay-HTML Component Imports

Jay-HTML Component Imports

Jay-HTML Component Imports

DesignerDesigner Agent Kit — documentation written for AI agents, readable by humans.

Headless Components

Headless components provide data and interactions with no UI. The page or headfull component provides the template.

Pattern 1: Key-Based Import

Data merged into parent ViewState under a key. Use when you have one instance of a component per page.

Declare in <head> with a key attribute:

<head>
  <script
    type="application/jay-headless"
    plugin="wix-stores"
    contract="product-page"
    key="productPage"
  ></script>
</head>

Access data and refs with the key prefix:

<h1>{productPage.productName}</h1>
<span>{productPage.price}</span>
<button ref="productPage.addToCartButton">Add to Cart</button>

<!-- Nested repeated sub-contracts -->
<div forEach="productPage.options" trackBy="_id">
  <h3>{name}</h3>
  <div forEach="choices" trackBy="choiceId">
    <button ref="choiceButton">{name}</button>
  </div>
</div>

Key-based imports are only available in pages (not in headfull FS components).

Important: Do NOT use <jay:keyName> for key-based imports. The key is for ViewState access ({key.field}), not for inline elements. <jay:> tags use the contract name, not the key.

Pattern 2: Instance-Based (jay: prefix)

Multiple instances with props and inline templates. Use when you need multiple instances or need to pass props.

Declare in <head> without a key:

<head>
  <script
    type="application/jay-headless"
    plugin="product-widget"
    contract="product-widget"
  ></script>
</head>

Use <jay:contract-name> tags with props:

<!-- Static props — each instance renders independently -->
<jay:product-widget productId="prod-1">
  <h3>{name}</h3>
  <div>${price}</div>
  <button ref="addToCart">Add</button>
</jay:product-widget>

<jay:product-widget productId="prod-2">
  <h3>{name}</h3>
  <button ref="addToCart">Add</button>
</jay:product-widget>

With bindings from page data (props from keyed components or page ViewState):

<!-- p is a keyed headless component providing product data -->
<jay:category-products categorySlug="{p.categorySlug}" limit="4">
  <div class="product-card">
    <h3>{name}</h3>
    <span>{price}</span>
  </div>
</jay:category-products>

Use {path} syntax to bind props to values from the page's ViewState. The binding is resolved at render time — works with both slow and fast phase data.

With forEach (dynamic props from parent data):

<div forEach="featuredProducts" trackBy="_id">
  <jay:product-widget productId="{_id}">
    <h3>{name}</h3>
    <div>${price}</div>
    <button ref="addToCart">Add</button>
  </jay:product-widget>
</div>

Inside <jay:...>, bindings resolve to that instance's contract tags (not the parent).

Choosing between patterns

Need Pattern Key? Tag?
One component per page, data across the whole template Key-based key="product" No <jay:> — use {product.field} bindings
Multiple instances, each with own props and template Instance-based No key <jay:contract-name prop="...">
One instance but with custom inline template Instance-based No key <jay:contract-name>

Never combine both: a component imported with key cannot also be used as <jay:>. These are mutually exclusive patterns.

Prop binding summary

Syntax Resolves to Example
prop="literal" Literal string value productId="prod-1"
prop="{field}" Page ViewState field slug="{p.categorySlug}"
prop="{field}" (inside forEach) ForEach item field productId="{_id}"

Prop phase constraints

Contract props can declare a phase (defaults to slow). The binding source must be available at that phase:

  • A slow prop (default) must bind to a literal, a route param, or a slow-phase tag
  • A fast prop can also bind to fast-phase tags

If a slow prop binds to a fast-phase field, jay-stack validate flags an error — the component's slow render would receive an empty value.

# In the component's contract:
props:
  - name: categorySlug
    type: string
    phase: slow # Must be available at build time
  - name: filter
    type: string
    phase: fast # Only needs to be available at request time

Headfull Components

In Jay Stack, headfull components are full-stack. They must have a .jay-contract file and are created using makeJayStackComponent in their .ts file. They support server rendering (slow/fast/interactive phases) and must include a contract attribute in the import.

Each headfull component lives in its own subdirectory under src/components/ with three files: .ts, .jay-html, and .jay-contract. The production build only discovers server-side component modules from src/components/ and src/plugins/. Placing them inside page directories will work in dev mode but fail in production.

Note: In Jay (without Jay Stack), headfull components use makeJayComponent and do not require a contract. However, makeJayComponent components should not be used in Jay Stack because they do not support server rendering.

Import Declaration

<head>
  <script
    type="application/jay-headfull"
    src="../components/shared-header/shared-header"
    names="SharedHeader"
    contract="../components/shared-header/shared-header.jay-contract"
  ></script>
</head>

Attributes:

  • src — Path to the component file (must include the filename, not just the directory)
  • names — Component name to import
  • contract — Path to the component's .jay-contract file (required in Jay Stack)

Usage

<jay:SharedHeader logoUrl="/logo.png" />

Route params: Headfull and instance-based headless components do not receive route params directly. To pass a route param, expose it through the page's ViewState and bind it as a prop: <jay:SideNav activePage="{activePage}" />. See routing.md for the full pattern.

Component Structure

Each headfull component needs three files in its subdirectory under src/components/:

.jay-contract — declares props. Tags are optional (use tags: [] or omit for structural components):

# components/site-header/site-header.jay-contract
name: SiteHeader
props:
  - name: logoUrl
    type: string
    required: true

.ts — component code. Must use makeJayStackComponent with .withProps() matching the contract props:

// components/site-header/site-header.ts
import { makeJayStackComponent, phaseOutput } from '@jay-framework/fullstack-component';
import type { SiteHeaderContract, SiteHeaderProps } from './site-header.jay-html';

export const siteHeader = makeJayStackComponent<SiteHeaderContract>()
  .withProps<SiteHeaderProps>()
  .withFastRender(async (props) => phaseOutput({ logoUrl: props.logoUrl }, {}));

For a structural component with only props and no data logic, .withFastRender passes props through as ViewState.

.jay-html — the template:

<!-- components/site-header/site-header.jay-html -->
<html>
  <head>
    <script type="application/jay-data" contract="./site-header.jay-contract"></script>
  </head>
  <body>
    <header>
      <img src="{logoUrl}" />
      <nav>Navigation here</nav>
    </header>
  </body>
</html>

Nesting Components

Headfull Inside Headfull

A layout component imports a header component:

<!-- layout/layout.jay-html -->
<html>
  <head>
    <script
      type="application/jay-headfull"
      src="../header/header"
      contract="../header/header.jay-contract"
      names="header"
    ></script>
    <script type="application/jay-data">
      data:
          sidebarLabel: string
    </script>
  </head>
  <body>
    <div class="layout">
      <jay:header logoUrl="/logo.png" />
      <aside>{sidebarLabel}</aside>
    </div>
  </body>
</html>

Headless Inside Headfull

A header component uses a headless plugin widget:

<!-- header/header.jay-html -->
<html>
  <head>
    <script type="application/jay-headless" plugin="my-plugin" contract="cart-indicator"></script>
    <script type="application/jay-data">
      data:
          logoUrl: string
    </script>
  </head>
  <body>
    <header>
      <img src="{logoUrl}" />
      <jay:cart-indicator>
        <span class="count">{itemCount}</span>
      </jay:cart-indicator>
    </header>
  </body>
</html>

Nesting depth is unlimited. Circular imports are detected as errors. Key-based headless imports (key="...") are not allowed inside headfull FS components — use instance-based imports instead.

Nesting Rules

Parent component Can import headfull FS? Can import headless (instance)? Can import keyed headless?
Page Yes Yes Yes
Headfull FS Yes (recursive) Yes (in its own head) No
Headless No (no template) No (no template) No (no template)

Complete Example

A homepage with key-based, instance-based, and headfull components:

<html>
  <head>
    <script
      type="application/jay-headless"
      plugin="mood-tracker"
      contract="mood-tracker"
      key="mt"
    ></script>
    <script
      type="application/jay-headless"
      plugin="product-widget"
      contract="product-widget"
    ></script>
    <script
      type="application/jay-headfull"
      src="../components/shared-header/shared-header"
      names="SharedHeader"
      contract="../components/shared-header/shared-header.jay-contract"
    ></script>
    <script type="application/jay-data" contract="./page.jay-contract"></script>
    <style>
      .section {
        margin: 20px 0;
        padding: 10px;
      }
      .product-card {
        border: 1px solid #ccc;
        padding: 10px;
        display: inline-block;
      }
    </style>
  </head>
  <body>
    <jay:SharedHeader logoUrl="/logo.png" />
    <h1>Homepage</h1>

    <!-- Key-based: mood tracker -->
    <div class="section">
      <div>Happy: {mt.happy} <button ref="mt.happy">more</button></div>
      <span if="mt.currentMood === happy">:)</span>
      <span if="mt.currentMood === sad">:(</span>
    </div>

    <!-- Instance-based: static product widgets -->
    <div class="section">
      <jay:product-widget productId="1">
        <h3>{name}</h3>
        <div>${price}</div>
        <span if="inStock">In Stock</span>
        <button ref="addToCart">Add</button>
      </jay:product-widget>
    </div>

    <!-- Instance-based: dynamic from forEach -->
    <div class="section">
      <div forEach="featuredProducts" trackBy="_id">
        <div class="product-card">
          <jay:product-widget productId="{_id}">
            <h3>{name}</h3>
            <div>${price}</div>
            <button ref="addToCart">Add</button>
          </jay:product-widget>
        </div>
      </div>
    </div>
  </body>
</html>

About this document

This page is part of the Jay Stack Agent Kit — documentation generated from the framework source and written primarily for AI agents. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation. Learn more about the Agent Kit →