Ship Design-System Elements
Shipping a Design-System Element
Plugin Developer Agent Kit — documentation written for AI agents, readable by humans.
A classic plugin component is headless: it ships a contract (and usually code) and provides no UI —
the project designer writes the template. A design-system element is the opposite end of the same
mechanism: your plugin also ships a template (.jay-html), and the designer flattens (copies) it into
their page rather than authoring the markup from scratch. The designer then owns the copy and tweaks it;
when you release a new version of the template, they pull your changes with jay-stack sync.
Use this when your component has an opinionated look you want to provide (a pricing card, a product tile, a callout) while still letting each project edit individual instances.
What you ship
A design-system element ships three things — the template is what makes it a design-system element:
| File | Required? | Purpose |
|---|---|---|
.jay-contract |
Yes* | Data shape, props, refs — the source of truth (as for any component) |
.ts |
Optional | Logic (makeJayStackComponent). Omit for a pure-markup element |
.jay-html |
Yes | The template the designer flattens — its <body> markup and <style> |
You ship a template, not a running UI. The
.jay-htmlis a source to copy from, not a live component that renders itself at the usage site. The designer's flattened copy is what compiles and renders. This is why upgrades flow throughsync(below) rather than automatically — the project owns its copy.
* The contract can be empty for a pure presentational element. It must exist (it names the element and is what the consumer's
contract=import resolves), but if the element binds no data and ships no.ts, the contract needs nothing but a name —name: HeroBannerplustags: [].
The template file
An ordinary .jay-html: a <head> declaring the contract, a <body> with the markup, and an optional
<style>. Bind {…} against your own contract tags/props.
<!-- pricing-card/pricing-card.jay-html -->
<html>
<head>
<script type="application/jay-data" contract="./pricing-card.jay-contract"></script>
</head>
<body>
<div class="pricing-card">
<h3 class="plan">{planName}</h3>
<p class="price">{price}</p>
<button class="cta">{ctaLabel}</button>
</div>
</body>
<style>
.plan {
font-weight: 600;
}
.cta {
background: var(--brand, #2b6cb0);
}
</style>
</html>
The component's CSS is copied alongside the markup and @scope-wrapped by the region's ref at the usage
site, so it never leaks into the project's other selectors.
How the designer consumes it
The designer imports the component with a template= provenance marker pointing at your shipped .jay-html,
places the region, and runs jay-stack sync to flatten it:
<script
type="application/jay-headless"
plugin="my-plugin"
contract="pricing-card"
template="./node_modules/@my-org/my-plugin/lib/components/pricing-card/pricing-card.jay-html"
></script>
<jay:pricing-card ref="pro" planName="Pro">…flattened copy, edited freely…</jay:pricing-card>
They edit the copy, marking any facet they want to keep with override (see the designer guide,
designer/design-system-guide.md). jay-stack validate reports unmarked edits as drift.
Upgrades flow through sync
Because the designer owns a copy, a change you ship in the template does not reach their page automatically. To pull your update, the designer runs:
jay-stack sync
Sync re-flattens every region from the current source template, preserving the facets the project marked
override and overwriting the rest. It is a deterministic overwrite-with-holes — no merge base, no
conflict resolution — so an upgrade can never silently mis-merge project edits with your changes.
Design your template for clean upgrades
- Keep structural churn low between versions. Sync preserves a marked facet by matching nodes positionally; large structural rewrites force the project to re-mark and re-review. Prefer additive, localized changes.
- Name meaningful hooks with
class. Projects override at the facet level (class, a style property,children). Stable class names make theiroverridemarks survive your edits. - Version your template with your package. The template ships from your package path; a project's
jay-stack syncreads whatever version is installed, so a normal dependency bump +syncis the whole upgrade path. - Document the intended override points in the contract
description— tell projects which parts are meant to be customized versus left to reconcile.
Declaring content slots — jay-content
override is the designer's tool, applied per page, per instance, to keep an edit you did not
anticipate. jay-content is your tool: put it on a template node to declare up front "this is the
consumer's to fill." Edits to a marked facet are then expected by design — validate never reports them as
drift, and sync keeps the consumer's version — without the designer marking anything. It is the
source-of-truth way to say "fill this in" for the parts of your template that are meant to vary.
It lives on the source template only. It is never copied onto the flattened page (sync strips it), so it is stated once and is resilient to your future template edits: as long as the marker stays, consumer content stays silent; remove it and that node falls back to the normal override model.
It is optional, not required. Unlike slot syntax in other frameworks, you do not have to annotate every
variable node — an unmarked spot still works, it just surfaces as drift until the consumer marks override
or runs sync. Add jay-content only where it earns its keep: a node genuinely meant to vary, where you'd
rather spare every consumer from repeating the same override.
Options (same facet-list grammar as override)
| You declare consumer-owned… | Write | Typical use |
|---|---|---|
| the children (text/markup) | jay-content (bare) |
a body copy / heading slot — the default |
| specific attributes | jay-content="src alt" |
a media <img> the consumer supplies |
| one inline-style declaration | jay-content="style.color" |
a themeable color the consumer sets |
| children and some attributes | jay-content="children src" |
a figure whose image and caption are both theirs |
| the whole node | jay-content="*" |
an entirely consumer-authored region |
Facets are space- or comma-separated (jay-content="src, alt"). The grammar is identical to override
with one difference: a bare jay-content means children (the common text slot), whereas a bare
override means the whole node. Use * when you really mean the whole node.
<!-- card.jay-html (your template) -->
<div class="ds-card">
<h3 class="ds-card__heading">{heading}</h3>
<p class="ds-card__body" jay-content>{body}</p>
<img class="ds-card__media" jay-content="src alt" src="placeholder.png" alt="" />
</div>
The consumer flattens this and edits the <p> text and the <img> src/alt with no override marks;
validate stays clean and sync preserves their copy. The class on each node is not a slot, so it
still reconciles with your source on every upgrade.
jay-content vs. override — which to reach for
- You (template author) declare the parts meant to vary with
jay-content, once, on the source. This is the preferred path for intended variation — it needs no per-page marks and survives your edits. - The designer uses
overrideon their page for an edit you did not declare a slot for (an unanticipated tweak they want to keep throughsync). Prefer giving them ajay-contentslot over leaving them tooverridethe same spot on every instance.
Design-system element vs. headless component
| Headless component | Design-system element | |
|---|---|---|
Ships a .jay-html template? |
No | Yes |
| Who writes the markup? | The project designer | You (project copies & edits it) |
| Import marker | contract= (+ optional key=) |
contract= and template= |
| Upgrades | Automatic (code is imported) | Via jay-stack sync (copy is owned) |
| Drift-checked / synced | No | Yes |
Both can be server-only or interactive, and both use the same .jay-contract. The only additional thing a
design-system element ships is the template — and the only additional thing the project runs is sync.
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 →