Skip to content

Compass Web SDK: Catch Up Card

The Catch Up card — <arc-compass-catchup-card>, one of the SDK-rendered Experiences shipped in @arcxp/compass-sdk — is a “while you were away” deck for returning readers. It recaps what has changed across the storylines a reader follows since their last visit, as a collapsed card that expands into a full-screen, Stories-style takeover.

Like every SDK-rendered Experience, it is a custom element you drop into your markup. The SDK fetches its content, renders it inside a shadow root, and gates the whole thing on reader consent — you place one tag and style it with CSS custom properties. There is no data to fetch and no card component to build.

What the card is

The card has two surfaces, both built from a single catch-up fetch:

  • The collapsed card — a compact deck with a featured story that autoplays through the reader’s moved storylines, a list of everything in this catch-up, and a “Catch me up” control. This is the entry point.
  • The takeover — a full-screen reader opened from “Catch me up.” It pages through the stories one screen at a time; the reader advances until they reach an “I’m caught up” confirmation and close back to your page.

The optional recs-surface-id attribute is the exception to “single fetch”: it adds a “You might also be interested in” list to the final caught-up screen, served by a separate recommendations request that is issued only if the reader reaches the end of the takeover. Budget for that extra call when you use it — see Attributes.

The card is personalized per reader and decides its own content: which storylines moved, in what order, and what to feature. You do not pass it a query, a section, or a count — it reads the consenting reader’s identity (established by the SDK) and requests their catch-up on mount.

Content in V1

The card presents real editorial content — the real headline, the real promo image, and story text extracted from the underlying content — for each item it shows. It is a recap of what your newsroom published, laid out for a returning reader.

Minimal render

With the SDK initialized and consent resolved (see the Integration Guide), the card is a single tag:

<arc-compass-catchup-card></arc-compass-catchup-card>

That is the entire integration. On mount, the element requests the current reader’s catch-up and renders the collapsed card. If the reader has nothing to catch up on — or has not consented — it renders nothing and collapses to zero height (see Lifecycle and reserving space).

Place it where a returning reader will meet it: the top of the homepage, a section front, or a personalized landing surface.

Attributes

Both attributes are optional and are read once, when the element first mounts — changing them afterward has no effect. The card takes no other inputs: there is no surface-id, count, or filter attribute on this Experience, because it does not take a query.

AttributeValuesEffect
recs-surface-idany stringEnables the “You might also be interested in” list on the final caught-up screen, fed by a separate recommendations request against this surface. Omit it and that list simply does not render; the rest of the card is unaffected.
open-storyany stringOpens the takeover directly on a specific story — a storyline key or the content item’s ID. Use it to restore a reader’s place: the takeover’s open state is not part of the URL, so a reader who follows a story out and returns lands on a freshly collapsed card unless you re-open it. A value that matches no story in the current catch-up is ignored, leaving the collapsed card.
<arc-compass-catchup-card
recs-surface-id="catchup-suggestions"
></arc-compass-catchup-card>

Theming

The card renders inside a shadow root: your page’s CSS cannot reach its internals, and its internals are not part of the API. You theme it through a set of --arc-compass-* CSS custom properties, which cascade through the shadow boundary — set them on the tag, a wrapping selector, or :root.

PropertyDefaultWhat it controls
--arc-compass-surfaceCanvas (system)Background where the card owns the surface — the full-screen takeover. Must be opaque.
--arc-compass-on-surfacecurrentColorInk on the surface: text, plus the neutral ramp (muted text, hairlines, subtle fills) derived from it
--arc-compass-accentcurrentColorPrimary accent — links, active states, and filled controls (“Catch me up,” “I’m caught up”)
--arc-compass-on-accent#fffForeground on an accent fill (button text/icon). Set it when your accent is light
--arc-compass-accent-secondarycurrentColorSupporting accent
--arc-compass-font-familyinherited page fontPrimary typeface — headlines and running text
--arc-compass-font-family-secondaryinherited page fontSecondary typeface — kickers, badges, labels, and meta

Set the surface/on-surface pair and the accent/on-accent pair and you are done: every neutral in the card is derived from those colours, so the component stays coherent on a light or dark page.

arc-compass-catchup-card {
--arc-compass-font-family: Georgia, serif;
--arc-compass-font-family-secondary: "Helvetica Neue", Arial, sans-serif;
--arc-compass-accent: #b00020;
}

The SDK never ships or @font-faces a typeface — load your fonts as you already do and point the tokens at them. Set only --arc-compass-font-family and the card uses that one face throughout.

Dark themes

The card’s colours come from your tokens, not the reader’s OS. To theme a dark edition, give it a dark surface, a light on-surface, and an on-accent that reads on your accent — that pair defines the background, the text, and every derived neutral:

.night-edition arc-compass-catchup-card {
--arc-compass-surface: #14161c; /* opaque page background */
--arc-compass-on-surface: #eef1f6; /* light ink */
--arc-compass-accent: #f2b705; /* amber */
--arc-compass-on-accent: #14161c; /* dark text on amber, not white */
color-scheme: dark; /* matches native chrome, e.g. the takeover scrollbar */
}

You do not need prefers-color-scheme or system colours to make the card’s UI dark — everything derives from the two colour pairs above. color-scheme only governs native browser chrome such as the takeover’s scrollbar.

No ::part() hooks

Unlike the recommendations Experiences, the Catch Up card exposes no ::part() hooks. Its themeable surface is the --arc-compass-* custom properties above and nothing else. The markup between them, class names, and any internal custom properties (anything prefixed --_) are internal and may change in any release. If the tokens cannot express a change you need, use the recipe integration rather than reaching into the shadow root.

The card is gated on the same single consent decision as the rest of the SDK. Until the reader has consented, nothing renders and no events are sent — the card is silent, not an error. See Compass Web SDK: Consent & Identity.

Events

Once consented, the card emits three analytics events through the SDK’s normal event pipeline:

EventWhen it fires
catch_up_shownThe card is shown to the reader (a qualified impression, or when they open the takeover).
catch_up_clickThe reader clicks through to one of the stories in the catch-up.
catch_up_dismissThe reader reaches the “I’m caught up” end state and finishes the takeover. An early close (the ✕, Escape, or navigating away) does not fire it — despite the name, there is no event for an early dismissal.

Each event carries an attributionContext — the card’s exposureId, plus the moved segment’s attributionId on catch_up_shown and catch_up_click — tagged source: "fy-catchup". That pairing lets a catch_up_click be confirmed against its catch_up_shown (a Catch-Up CTR loop), and one catch_up_shown fires per material segment rather than one per card. The SDK emits all of this itself: you do not tag anything, populate attributionContext, or call track() for the card.

Lifecycle and reserving space

The card never shows a spinner, a skeleton, or an error message. It renders its content or it renders nothing, collapsing to zero height. That means there is no visual signal for your code to read — the SDK’s lifecycle events are that signal. They bubble, so you can listen on a container and read event.target:

EventMeaning
arc-compass:renderedThe card is in the DOM.
arc-compass:emptyNothing to catch up on for this reader — a successful answer, not a failure.
arc-compass:errorSomething went wrong; the element is collapsed.
arc-compass:catchup-doneThe reader finished the takeover (“I’m caught up”). May fire later, or never.

Exactly one of rendered / empty / error fires per mount once the reader has consented — the card renders behind the consent gate, so until consent resolves to granted (and never, if it is denied) it stays silent and dispatches none of the three. catchup-done is separate: it describes a reader finishing, fires later than the terminal event, and may not fire at all (a reader who never opens the takeover never completes it). Like the others, these events carry no detail.

If the card sits above the fold, reserve its space yourself and release it on the terminal event so an empty or failed response leaves no gap:

<div class="catchup-slot">
<arc-compass-catchup-card recs-surface-id="catchup-suggestions"></arc-compass-catchup-card>
</div>
.catchup-slot {
min-height: 360px;
}
.catchup-slot.settled {
min-height: 0;
}
document.addEventListener("arc-compass:empty", release);
document.addEventListener("arc-compass:error", release);
function release(event) {
event.target.closest(".catchup-slot")?.classList.add("settled");
}

Release on empty and error only — on rendered the content is already filling the slot.

A realistic integration

Putting it together: a themed card, above-the-fold space reserved and released on the terminal events and on the two non-granted consent outcomes (so a reader who denies or ignores consent is not left with a permanent gap), a suggestions surface on the caught-up screen, and a resume hook so a reader who follows a story out and comes back re-opens where they were.

<!-- The card is created in the script below so `open-story` is set *before* the
element is inserted — the attribute is read once when the card mounts and is
not observed afterward. If you don't need resume, drop the script and place
<arc-compass-catchup-card recs-surface-id="catchup-suggestions"> here. -->
<div class="catchup-slot"></div>
<style>
.catchup-slot {
min-height: 360px;
}
.catchup-slot.settled {
min-height: 0;
}
arc-compass-catchup-card {
--arc-compass-font-family: "Publico", Georgia, serif;
--arc-compass-font-family-secondary: "Franklin", Arial, sans-serif;
--arc-compass-accent: #b00020;
--arc-compass-on-accent: #ffffff;
/* Set the surface pair so the full-screen takeover matches your page. */
--arc-compass-surface: #ffffff;
--arc-compass-on-surface: #1a1a1a;
}
</style>
<script>
document.addEventListener("arc-compass:empty", release);
document.addEventListener("arc-compass:error", release);
function release(event) {
event.target.closest(".catchup-slot")?.classList.add("settled");
}
// Those events never fire when consent is denied or never answered, so also
// collapse the reserved slot in both of those cases (see the consent caution
// above). The SDK has no consent-denied event — define a handler and wire it
// to your CMP's denial callback (the exact API differs per CMP).
function collapseReservedSlots() {
document.querySelectorAll(".catchup-slot").forEach((s) => s.classList.add("settled"));
}
// yourCmp.onDenied(collapseReservedSlots); // ← wire this up to your consent manager
// Never answered: after a grace period, collapse whatever is left — but only
// while consent is still *unresolved*. A granted decision (even one whose card
// is still loading) or a denial is already handled above, so bail if the reader
// has since answered; otherwise you risk yanking a slot a slow-but-granted
// render is about to fill.
setTimeout(() => {
if (yourCmp.consentAnswered) return; // ← your CMP's "reader has responded" flag
document
.querySelectorAll(".catchup-slot:not(.settled)")
.forEach((s) => s.classList.add("settled"));
}, 10000);
// Optional: restore the reader's place after they return from a story.
// `open-story` is read once, when the card mounts (its connectedCallback — the
// moment it is inserted into the DOM) and is not observed afterward, so setting
// it on an element already in the page has no effect. The only requirement is
// that it be present *before* the element is appended; it does not matter
// whether ArcCompass.init() has run yet. Creating the element, setting the
// attribute, then appending guarantees the value is there at mount.
//
// The read side needs a value to read, so persist it elsewhere in your app:
// when the reader opens a story from the catch-up (your own routing knows which
// one), stash its id under this key, and clear it once they are caught up —
// sessionStorage.setItem("catchup:open-story", storyId); // when a story opens
// sessionStorage.removeItem("catchup:open-story"); // when caught up
const resumeKey = sessionStorage.getItem("catchup:open-story");
const card = document.createElement("arc-compass-catchup-card");
card.setAttribute("recs-surface-id", "catchup-suggestions");
if (resumeKey) card.setAttribute("open-story", resumeKey);
document.querySelector(".catchup-slot")?.append(card);
// Clear the resume key once the reader finishes the takeover, so the next visit
// opens a fresh collapsed card instead of re-opening the story they already
// caught up on. `arc-compass:catchup-done` fires on "I'm caught up" (see
// Lifecycle and reserving space); it bubbles, so listening on the card works.
card.addEventListener(
"arc-compass:catchup-done",
() => sessionStorage.removeItem("catchup:open-story"),
{ once: true },
);
</script>

MVP limitations

  • Single-site. The card serves one site’s catch-up per placement.
  • Enablement is provisioning-driven. Catch Up is turned on for your site by Arc during provisioning — there is no self-serve toggle. Contact your Arc XP representative to enable it. Once enabled, the card begins returning content for qualifying readers; before then it renders nothing (an arc-compass:empty).

See also