A guide for front-end developers building banner layouts for upload to a Design System in Activator. Banner layouts are a form of custom development — unlike layouts for other channels, they are authored in HTML/CSS (and GSAP for animation) outside Activator and then uploaded to the Design System.
This guide covers:
-
The mental model — banners, scenes, layouts, fields
-
Building a static layout
-
Building an animated layout with
<fusion-timeline>and thedata-tl-*attribute family -
Authoring a content-module fillable region (typically ISI) so an editor user can swap in a content module
-
Packaging your layout for upload
-
A checklist and the common pitfalls to avoid
Audience. This page is for front-end developers building banner layouts. If you are a content author looking for how banner layouts appear in the editor, see Banner Layouts. If you are looking for how the banner channel works at a product level, see the Banners channel overview.
1. Concepts
Banner
An HTML ad delivered to ad platforms (e.g. Google Ads, Studio). A banner in Activator targets multiple viewports — for example 300×250, 300×600, 728×90 — from a single document. Each viewport is its own HTML rendition and has its own set of compatible layouts.
A banner has two representations:
-
Animated — full motion banner driven by
<fusion-timeline>and GSAP. -
Static — single-frame HTML, no JavaScript. Used for ad platforms that don't accept animation.
Both can live in the same banner document. A layout you author is tagged for one or both.
Scene
A scene is a slice of the banner's timeline — typically one "moment" of the banner, e.g. headline reveal, product shot, CTA. Scenes play in sequence and the last one usually stays visible at the end.
In the DOM, a scene is a <fusion-timeline> element wrapped around a registered state:
<fusion-timeline data-act-scene="" data-fusion-state="scene-{uuid}" allow-layout="true">
<!-- your layout goes here -->
</fusion-timeline>
Activator's editor manages this wrapper through the Scene Manager. You do not write the <fusion-timeline> scene wrapper in your layout source. You write only the content that goes inside it.
Layout
A layout is a reusable, pre-built HTML unit that authors drop into a scene. For banner layouts specifically:
-
A layout is everything that goes inside the scene's
<fusion-timeline>wrapper — and not the wrapper itself. -
A layout is not its own
<fusion-timeline>— but it may contain animated child elements (viadata-tl-*attributes) that are interpreted by the surrounding scene timeline. -
A layout can contain multiple HTML variants — one file per supported viewport size — plus metadata for whether it targets animated banners, static banners, or both. This metadata is set at upload time. See §2 for how variants are packaged.
Field
A field is a DOM element whose content Activator authors can replace from the editor. Two attributes mark an element as a field:
fieldname="Headline" show-in-editor="true"
Use the right HTML element for the content type: Activator decides how to apply replacement content based on the element's tag.
|
Content type |
Element to use |
What gets replaced |
|---|---|---|
|
Text |
|
Text content |
|
Image |
|
|
|
Link / CTA |
|
Text + (for |
Content-module fillable region
A special kind of field where the body copy is swappable from a content module at edit time. Marked with an extra attribute (allow-content-module="true") on top of the usual field attributes. Typically used for ISI (Important Safety Information) regions, but the mechanism is generic — any region can be made content-module-fillable.
See §6 Content-module fillable regions below for the full markup and worked example.
Attributes Activator adds — do not write these yourself
When your layout is dropped into a document, Activator decorates the layout's root element with:
is-layout="true"
layout-name="…"
layout-group="…"
layout-description="…"
id="WES_…"
These do not belong in your source. Author your layout root as a plain <div> (or whatever wrapper your layout needs). Activator adds the rest on insert.
2. The layout file
A layout is uploaded to a Banner Design System as a zip that can contain one HTML file per supported viewport size — a layout variant per viewport:
my-layout.zip
├── 300x250.html
├── 300x600.html
└── 728x90.html
A layout that only supports one viewport still ships the same way, just with a single variant file instead of several.
Each variant file is an HTML fragment, not a full document. No <!DOCTYPE>, no <html>, no <head>, no <body>. The fragment is everything that lives inside the scene's <fusion-timeline> wrapper at runtime, for that specific viewport.
Minimal shell (one variant — e.g. 300x600.html):
<!-- 300x600.html -->
<div style="position: relative; width: 300px; height: 600px;">
<!-- layout content -->
</div>
Notes:
-
All styling should be inline or in classes scoped to your layout. Banner viewports are tight; avoid global selectors that could collide with sibling layouts in the same document.
-
Reference shared assets (logos, fonts) via the Design System's shared asset paths, as in the worked examples below.
-
GSAP is loaded by the surrounding banner document at runtime — you do not include it in your layout fragment.
-
Variant filenames must match the viewport size they target, using the naming convention Activator expects (see §8), so each file maps to the right viewport at upload.
3. Static layouts
A static layout is plain HTML + CSS, with fields where Activator authors can plug in content. No JavaScript, no timeline.
3.1 Step by step
-
Decide which viewport(s) the layout targets (e.g. 300×600).
-
Build the layout HTML at that exact pixel size. Use
position: absolute/position: relativeso the layout doesn't depend on a parent flexbox or grid. -
Mark every replaceable element with
fieldname="…"andshow-in-editor="true". Use the right element for each content type. -
Keep the layout self-contained — no globals, no imports.
-
If the layout targets more than one viewport, repeat for each size and save each as its own variant file. Zip and upload (see §8 Packaging).
3.2 Complete static example (300×600)
<!-- Outer shadow wrapper -->
<div style="
position: relative;
width: 300px;
height: 600px;
overflow: hidden;
">
<!-- Banner container -->
<div style="
position: absolute; top: 0; left: 0;
width: 300px; height: 600px;
overflow: hidden; background: #000;
">
<!-- Background image field -->
<img
fieldname="Background Image"
show-in-editor="true"
src="../shared/assets/images/image.png"
alt=""
style="
position: absolute;
top: -0.07%; left: -182.02%;
width: 404.18%; height: 113.38%;
max-width: none;
pointer-events: none;
"
/>
<!-- Headline + CTA -->
<div style="
position: absolute;
top: calc(50% - 45px); left: 15px;
display: flex; flex-direction: column; gap: 16px;
">
<p fieldname="Headline" show-in-editor="true" style="
margin: 0; width: 182px;
font-family: 'Arial Black', sans-serif;
font-size: 35px; line-height: 1; color: #fff;
">Start your momentum</p>
<div style="
display: inline-flex;
background: #d8eb61;
border-radius: 142px;
padding: 7px 18px;
">
<p fieldname="CTA" show-in-editor="true" style="
margin: 0; font-size: 14px; color: #000;
">Learn More</p>
</div>
</div>
<!-- Brand logo (not a field — fixed) -->
<img
src="../shared/assets/images/logo.png"
alt="Brand"
style="position: absolute; top: 22px; left: 11px; width: 140px;"
/>
</div>
</div>
Three fields here: a Background Image (<img> → src is replaced), a Headline (<p> → text is replaced), and a CTA (<p> → text is replaced).
4. Animated layouts
An animated layout has the same shape as a static layout, but its child elements are annotated with data-tl-* attributes that the surrounding <fusion-timeline> scene timeline reads to build a GSAP animation.
Important: Your layout does not include a <fusion-timeline> of its own. The scene wrapper is supplied by Activator. You only write the children that animate within it.
4.1 The animation attributes you'll use
|
Attribute |
Purpose |
|---|---|
|
|
When and how the element enters. Maps to |
|
|
When and how it exits. Maps to |
|
|
Instant zero-duration property set. Maps to |
|
|
Adds a named label at the element's entry time. Requires |
|
|
Skip default visibility clipping. Values: |
Each tween attribute takes a JSON object:
{
"at": 1.2,
"vars": { "opacity": 0, "y": 24 },
"duration": 0.5,
"ease": "power2.out"
}
at is absolute seconds within the scene's own timeline — measured from t = 0 of this scene, not from the start of the whole banner. Each scene is a nested <fusion-timeline> with its own coordinate system; you author every element's time as if this scene plays in isolation. Activator's scene wrapper handles where the scene sits on the parent banner timeline.
4.2 Visibility clipping — what you get for free
Children of a scene that have no real entry animation are automatically hidden until the scene starts (entry clip). Children with no real exit animation are automatically hidden when the scene ends (exit clip). Both clips are added by the scene wrapper — you don't write them.
On the last scene of a non-looping banner, both auto-clips are skipped (recursively, for the last nested timeline at every level) — so any element without explicit entry/exit tweens stays visible at the end. Note that this only affects the auto-clips; explicitly authored data-tl-exit tweens still run (see §4.3).
If you need an element to survive across scene boundaries — a persistent logo, a watermark — add data-tl-persist="after" (or "both").
4.3 What "last scene" changes (and doesn't)
Only automatic visibility clipping behaves differently on the last scene of a non-looping banner — the end-of-scene autoAlpha: 0 clip is skipped, so any element without an explicit data-tl-exit stays visible at the end.
Explicit data-tl-exit tweens always run, regardless of scene position. If you put an exit on an element and the layout lands in the final scene, the exit fires and the element animates out — which usually isn't what you want for a closing frame.
Practical rule for authoring layouts that may be used in any position, including last:
-
Animate elements in with
data-tl-entry. -
Let the automatic exit clip hide them when the scene ends in a non-final position. It's skipped on the last scene, so the elements simply stay visible.
-
Only add
data-tl-exitif you genuinely want the element to animate away — and accept that it will animate away even when the scene lands last.
4.4 Step by step
-
Build the layout at the target viewport size, just like a static layout. Position absolutely.
-
Mark fields with
fieldname+show-in-editor="true". -
For each element that animates in, add
data-tl-entry='…'with an absoluteattime and avarsobject describing its hidden / starting state (GSAPfrom()semantics — the element animates fromvarsto its CSS-rendered state). -
For each element that animates out, add
data-tl-exit='…'with an absoluteattime and thevarsdescribing its target hidden state. -
Use
data-tl-persiston anything that should survive between scenes. -
Author every
attime relative to this scene's own timeline starting att = 0— don't try to coordinate with neighbouring scenes. Activator places the scene on the parent banner timeline.
5. Worked example: animated 300×600 layout
A three-element scene: a kicker fades in, the headline slides up, a background tint exits when the scene ends.
<!-- 300x600.html — animated layout fragment -->
<div style="font-family: 'Geist', system-ui, sans-serif; color: #1a0f3d;
position: relative; width: 300px; height: 600px;">
<!-- Background tint. Has only an exit — the auto entry clip handles its
reveal at scene start. -->
<div
style="position: absolute; inset: 0; background: #d8eb61;
pointer-events: none;"
data-tl-exit='{"at":1.6,"vars":{"autoAlpha":0},"duration":0}'
></div>
<!-- Brand block. Animates in, then exits. -->
<div
style="position: absolute; top: 24px; left: 24px; right: 24px;
display: flex; align-items: center; gap: 8px; z-index: 10;"
data-tl-entry='{"at":0,"vars":{"autoAlpha":0},"duration":0.1}'
data-tl-exit='{"at":1.6,"vars":{"autoAlpha":0},"duration":0}'
>
<img
fieldname="Brand Logo"
show-in-editor="true"
src="../shared/assets/images/logo.svg"
alt="Brand"
style="width: 24px; height: 24px;"
data-tl-entry='{"at":0,"vars":{"opacity":0,"y":-12},"duration":0.4}'
/>
<div style="line-height: 1;"
data-tl-entry='{"at":0.1,"vars":{"opacity":0,"y":-12},"duration":0.4}'>
<div style="font-weight: 600; font-size: 18px;">Brandname</div>
<div style="font-size: 9px; opacity: 0.7;">Generic name</div>
</div>
</div>
<!-- Headline block. Kicker fades up, headline slides up. -->
<div
style="position: absolute; top: 110px; left: 24px; right: 24px;
bottom: 110px; display: flex; flex-direction: column;
justify-content: center;"
data-tl-exit='{"at":1.2,"vars":{"opacity":0,"y":-32},"duration":0.4}'
>
<p
fieldname="Kicker"
show-in-editor="true"
style="margin: 0; font-size: 13px; opacity: 0.85;"
data-tl-entry='{"at":0,"vars":{"opacity":0,"y":12},"duration":0.4}'
>Find your</p>
<p
fieldname="Headline"
show-in-editor="true"
style="margin: 0; font-weight: 700; font-size: 50px;
line-height: 0.92; letter-spacing: -0.03em;"
data-tl-entry='{"at":0.4,"vars":{"opacity":0,"y":24},"duration":0.5}'
>rhythm.</p>
</div>
</div>
What's happening:
-
The scene effectively runs from
t = 0to roughlyt = 2.0(last entry ends ~0.9, last exit ends ~1.6). -
The yellow background has no entry tween, so the auto entry clip flashes it visible at scene start, and its
autoAlpha: 0exit fades it out att = 1.6. -
The headline block has no entry but does exit — the auto entry clip reveals it at scene start, then the exit animates it up and out at
t = 1.2. -
The brand logo and wordmark cascade in over the first 0.5s using individual
data-tl-entrytweens.
This layout is shaped for a mid-sequence scene: every visible block has an explicit data-tl-exit, so it transitions cleanly into whatever scene comes next. If it lands in the final scene position, those exits still run and the screen clears — see §4.3. To make a layout that works in any position, including last, drop the data-tl-exit attributes from elements you want visible on the closing frame and let the automatic exit clip handle them in non-final positions.
6. Content-module fillable regions (e.g. ISI)
An ISI (Important Safety Information) region is a scrollable block of body copy that often needs to be identical and reusable across many banners. Rather than hand-editing the copy in every banner, you mark one region of the layout as fillable from a content module. The flow has two sides:
-
Authoring (this section's main focus): the layout developer adds a small set of attributes to the HTML so the editor recognises the region as a swappable content slot.
-
Editing: an editor user opens the banner, finds that field in the panel, and attaches a content module — its body replaces the placeholder copy.
The mechanism is generic: "ISI" is just the most common use of it. Any region can be made content-module-fillable the same way.
6.1 The markup convention
Three attributes turn a plain element into an editable, content-module-fillable slot:
|
Attribute |
Purpose |
|---|---|
|
|
Human-readable label for the field in the editor's fields panel. |
|
|
Makes the element appear as an editable field in the editor. |
|
|
Marks the field as fillable from a content module (shows the modular-content control). |
6.2 Region anatomy
A typical ISI region is a self-contained, scrollable widget. The content-module slot is the inner wrapper — not the outer container:
#isi-container scroll widget (position, entry/exit animation)
├── #head header bar
│ └── <p fieldname="ISI Headline" show-in-editor="true"> Important Safety Information
├── #isi-scroller overflow-y: scroll viewport
│ └── #isi-content text styling wrapper
│ └── <div fieldname="ISI Content" show-in-editor="true" allow-content-module="true">
│ ↑ THE SWAPPABLE SLOT — body copy lives here
│ ... optional footer (logo, prescribing-info links) ...
└── #isi-scrollerbar custom scrollbar (#isi-scrollbar / #isi-scrubber)
+ a small GSAP script wiring scroll → scrubber position
6.3 Minimal snippet
The slot is a single wrapper element carrying the three attributes. Everything inside it is placeholder copy that an editor user can later replace with a content module:
<div id="isi-container"
style="width: 300px; height: 47px; bottom: 13px; left: 0; position: absolute; z-index: 5;"
data-tl-entry='{"at": 1, "vars": {"y": 250}}'
data-tl-persist="after">
<div id="head" style="width: 300px; height: 18px; background-color: #2B034D; position: absolute; top: -18px;">
<p fieldname="ISI Headline" show-in-editor="true"
style="font-weight: 700; color: #fff; font-size: 12px; padding: 2px 11px;">Important Safety Information</p>
</div>
<div id="isi-scroller"
style="width: 100%; height: 100%; background-color: #fff; overflow-y: scroll; overflow-x: hidden; padding-right: 17px;">
<div id="isi-content"
style="position: relative !important; padding: 4px 11px 0; font-size: 11px; line-height: 14px; color: #2B034D;">
<!-- The swappable content-module slot -->
<div fieldname="ISI Content" show-in-editor="true" allow-content-module="true">
<p>Your safety information copy goes here. This placeholder is replaced when an
editor user attaches a content module to this field.</p>
</div>
</div>
</div>
</div>
6.4 Do / don't
-
Do put the three attributes on the inner content wrapper (the element directly holding the body copy).
-
Don't put
allow-content-module="true"on#isi-containeror#isi-scroller— the slot must wrap only the swappable copy, so the header, scrollbar, and footer stay fixed. -
ISI/content-module regions belong to animated banner layouts. Static banner layouts do not use them.
-
Keep meaningful placeholder copy in the slot so the banner is reviewable before a module is attached.
Which attribute switches the control on today. allow-content-module="true" is the forward-looking marker and is what every ISI banner layout already uses — author to it. Be aware that in the editor today the modular-content control actually appears when the element carries a content-module-id attribute; recognising allow-content-module on its own is still being rolled out. So if the control doesn't appear yet from allow-content-module alone, that's expected — keep using the attribute. This section will be updated once it is fully supported.
6.5 Auto-scrolling the ISI
In the standard ISI layouts, the ISI region auto-scrolls: once the banner's entrance animations finish, the body copy scrolls slowly from top to bottom so the full safety information is revealed without any user interaction. User input (mouse wheel or arrow keys) pauses the auto-scroll so the reader stays in control.
The trigger is the fusion-timeline-complete event that the platform dispatches on #root when the top-level animation timeline finishes. Nested timelines are ignored via event.detail.isNested. A paused GSAP timeline then animates isiScroller.scrollTop from top to bottom:
const root = document.getElementById('root');
const isiScroller = document.getElementById('isi-scroller');
const scrollTL = gsap.timeline({ paused: true });
let scrollY = { y: 0 };
// Start auto-scroll only when the TOP-LEVEL banner timeline completes
root.addEventListener('fusion-timeline-complete', (event) => {
if (event.detail.isNested) return; // ignore nested timelines
updateValues(); // (re)compute scrollable height
playScrollTL(); // begin the slow auto-scroll
});
function playScrollTL() {
const duration = ((contentHeight - scrollY.y) / contentHeight) * 180;
scrollTL.play();
scrollTL.to(scrollY, {
duration, y: contentHeight, ease: 'none',
onUpdate: () => { isiScroller.scrollTop = scrollY.y; },
});
}
// Pause auto-scroll the moment the user scrolls or uses arrow keys
isiScroller.addEventListener('wheel', () => scrollTL.pause());
This auto-scroll is not Activator-provided functionality. Activator/Fusion only dispatches the fusion-timeline-complete lifecycle event. The auto-scroll itself — the GSAP timeline, the custom scrollbar/scrubber sync, the duration calculation, and pausing on user interaction — is custom JavaScript that must be built into the layout. When adding an ISI region to a new banner, copy this logic from an existing ISI layout; it will not work from the markup attributes alone.
6.6 Attaching a content module (editor UX)
This is what happens after the layout is handed off — how an editor user fills the slot. Once a banner with an ISI slot is opened in the editor:
-
The Content Modules feature must be enabled for the workspace. If it's off, the modular-content control never appears.
-
The fields panel lists the slot under its
fieldname(e.g. ISI Content). A modular-content icon button appears next to fields that support content modules. -
Clicking it opens the modular content popover, a browser of available content-module assets.
-
Selecting an asset attaches it: the editor writes
content-module-idandcontent-module-asset-idonto the element, and the asset's body replaces the placeholder copy. -
A Detach action clears both attributes and restores the field to a normal editable region.
7. Click tags
Click tags are the mechanism banners use to handle clicks, letting ad networks override the destination URL at serve time. Content authors manage click tags entirely from the editor, so there is not much for you to do here, but two pieces of markup matter for how your layout supports them.
7.1 The banner-wide click tag
Give your layout's clickable wrapper id="click-tag". That's the element the banner-wide tag binds to, so the whole banner becomes clickable through it.
7.2 Element-level click tags
These are attached by content authors in the editor, not by you. The editor writes a plain data-click-tag="<name>" attribute onto whichever element they choose. You don't need to add this attribute yourself, and no special markup makes an element eligible; any element can receive one.
7.3 The managed script block
The editor maintains a single script block, <script id="act-click-tags">, in each rendition. It declares one variable per tag and binds the click handlers. This script is generated and rewritten automatically whenever tags change, so:
-
Don't hand-edit or duplicate it.
-
Don't worry about a stale
data-click-tagattribute left behind by a rename or delete. It's simply ignored, not an error. -
The script does nothing while the document is open in the editor, so click handlers never interfere with editing. It only becomes active in the published banner.
8. Packaging and upload
-
Save each viewport's fragment as its own HTML file, named for the viewport it targets (e.g.
300x600.html). -
Zip all variant files together, directly at the root of the zip — no subfolders:
layout-rhythm.zip ├── 300x250.html ├── 300x600.html └── 728x90.htmlA single-viewport layout zips the same way, just with one file at the root.
-
Upload the zip to a Banner Design System in Activator. Each variant's target viewport is read from its filename, so at upload time you (or the Design System owner) attach the remaining metadata that controls editor visibility:
-
Whether the layout is for animated banners, static banners, or both
-
Display name, group, description, and animation description
-
In Activator, authors will see your layout in the layouts panel only when a variant matches the active scope (the viewport or viewports currently being edited) and the animated/static toggle matches what the layout supports. See Banner Layouts for the filtering behaviour.
9. Checklist before zipping
- Each variant's HTML is a fragment — no
<!DOCTYPE>,<html>,<head>, or<body>. - No
<fusion-timeline>element in your layout — the scene wrapper is added by Activator. - Layout root is a plain
<div>(or appropriate wrapper). Nois-layout,layout-name,layout-group, orlayout-descriptionattributes in source. - Every replaceable element has both
fieldname="…"andshow-in-editor="true". - Field element type matches the content type (
<img>for image, text element for text,<a>for links). - All sizes are absolute / fixed to the target viewport.
- CSS is inline or scoped — no global selectors.
- (Animated only) Every
data-tl-*attribute is valid JSON withatin absolute seconds within the scene's own timeline. - (Animated only) Elements that should survive across scene boundaries use
data-tl-persist. - (Content-module slots) The three attributes (
fieldname,show-in-editor="true",allow-content-module="true") are on the inner content wrapper, not the outer container. - (ISI regions) Auto-scroll JavaScript is included in the layout — it's not provided by Activator.
- Zip contains one HTML file per supported viewport, each named for its viewport, at the root of the zip — nothing else.
- Clickable wrapper has
id="click-tag"if the layout should support the banner-wide click tag.
10. Common pitfalls
Including a <fusion-timeline> in your layout. The scene already is one — wrapping the layout in another timeline nests it and breaks scene timing. Use data-tl-* attributes directly on child elements.
Using relative time references. data-tl-entry.at is absolute seconds within the scene's own timeline, measured from this scene's t = 0. GSAP's relative-position strings ("+=0.5", "<") are not supported here — the visual timeline tool needs absolute times to render.
Forgetting show-in-editor="true". Without it, an element with fieldname may be ignored as a field.
Wrong element type for a field. Putting fieldname="Headline" on an <img> will not produce the result you want when text content is applied. Use the table in §1.
Global CSS that leaks across layouts. Multiple layouts coexist in the same banner viewport iframe. Scope styles to your layout root or use inline styles.
Putting allow-content-module="true" on the wrong wrapper. The slot must wrap only the swappable copy — never the outer scroll container. Otherwise the header, scrollbar, and footer disappear when a module is attached.
Expecting ISI auto-scroll to work from markup alone. The auto-scroll is custom JavaScript that must be built into the layout. Copy the pattern from an existing ISI layout.
11. Attribute reference
|
Attribute |
Set by |
Meaning |
|---|---|---|
|
|
Author |
Label shown for the field in the editor. |
|
|
Author |
|
|
|
Author |
|
|
|
Author |
Entry tween (JSON). Maps to GSAP |
|
|
Author |
Exit tween (JSON). Maps to GSAP |
|
|
Author |
Zero-duration property set (JSON). Maps to GSAP |
|
|
Author |
Named label at the element's entry time. |
|
|
|
|
|
|
Editor |
ID of the attached content module. Written when an editor user attaches one and cleared on detach. Currently also what makes the modular-content control appear. |
|
|
Editor |
ID of the specific asset within the module. |
|
|
Activator |
Added on insert. Do not write these in source. |
|
|
Author |
Marks the banner's clickable wrapper. The banner-wide click tag binds to this element. |
|
|
Editor |
Written by the editor when a content author attaches a secondary click tag to an element. Not something you write in source. |
Related
-
Banner Layouts — how banner layouts appear in the editor and are filtered by active scope.
-
Banners — the Banners channel overview.
-
Scene Manager — how scenes are managed in the editor.
-
Banner Viewports & Renditions — how viewports are configured and stored.
-
Publishing a Banner — publishing flow and distribution package structure.
-
Click Tags in Banners — the content-author-facing view of click tags.