# Compass Web SDK: Theming Reference

Source: https://dev.arcxp.com/content-recommendations/sdk/theming/

One theming reference for every SDK-rendered Experience — arc-compass-recommendations-carousel, arc-compass-recommendations-list, and arc-compass-catchup-card: the --arc-compass-* tokens and where each applies, a copy-paste brand block with light and dark recipes, the shadow boundary, and ::part() styling from plain CSS or Tailwind.

Every SDK-rendered Experience shares one theming contract. Set your brand once and all three pick it up:

- `<arc-compass-recommendations-carousel>` and `<arc-compass-recommendations-list>` — see [Recommendations Components](https://dev.arcxp.com/content-recommendations/sdk/recommendations-components/).
- `<arc-compass-catchup-card>` — see [Catch Up Card](https://dev.arcxp.com/content-recommendations/sdk/catch-up-card/).

Each Experience 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 two public surfaces only:

1. **CSS custom properties** (`--arc-compass-*`), which cascade through the shadow boundary. Set them on the tag, a wrapping selector, or `:root`.
2. **`::part()` hooks** on named elements, for restyling an element outright. Each component page lists its own part names.

Everything else — the markup between the parts, class names, and internal custom properties (anything prefixed `--_`) — is internal and can change in any release.

## Tokens

One table for all three Experiences. The **Applies to** column says where a token has a visible effect; setting a token on an Experience that does not use it is harmless, so a single brand block can target all three.

| Property                             | Default              | What it controls                                                                                                                                                                                                             | Applies to      |
| ------------------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| \--arc-compass-surface               | Canvas (system)      | Background where Compass owns the surface — the full-screen Catch Up takeover. **Must be opaque.**                                                                                                                           | Catch Up Card   |
| \--arc-compass-on-surface            | currentColor         | Ink on the surface: text, plus the neutral ramp (muted text, hairlines, subtle fills) derived from it.                                                                                                                       | Catch Up Card   |
| \--arc-compass-accent                | currentColor         | Primary accent. Colors the kicker above each recommendation headline, and the Catch Up Card’s links, active states, and filled controls (“Catch me up,” “I’m caught up”).                                                    | All three       |
| \--arc-compass-on-accent             | #fff                 | Foreground on an accent fill (button text and icons). Set it when your accent is light.                                                                                                                                      | Catch Up Card   |
| \--arc-compass-accent-secondary      | the primary accent   | Premium badge on recommendation cards, including the Catch Up Card’s “You might also be interested in” list. **Deprecated** — see [The secondary accent](#the-secondary-accent).                                             | All three       |
| \--arc-compass-font-family           | inherited page font  | Primary typeface — headlines and running text.                                                                                                                                                                               | All three       |
| \--arc-compass-font-family-secondary | the primary typeface | Secondary typeface — kickers, badges, labels, and meta.                                                                                                                                                                      | All three       |
| \--arc-compass-radius                | .5rem                | Corner rounding — cards, images, and buttons. See [Corner radius](#corner-radius).                                                                                                                                           | All three       |
| \--arc-compass-card-min-width        | 220px                | Minimum card width in the recommendations grid — the one density knob. See [Sizing the cards with one value](https://dev.arcxp.com/content-recommendations/sdk/recommendations-components/#sizing-the-cards-with-one-value). | Recommendations |

The recommendations Experiences are transparent and draw text in your page’s own color, so they do not read the `surface`/`on-surface` pair. The Catch Up Card is the only Experience that paints a surface of its own.

### Zero-config appearance

With no tokens set, Experiences render “native”: text follows your page’s color, both accents fall back to that same color, and running text inherits your page’s font, so an unthemed component is monochrome but never clashes. The one exception is the Catch Up Card’s typography: until you set the font tokens, its headlines use Georgia and its intro line uses Inter, its built-in look.

The SDK never ships or `@font-face`s a typeface. Load your fonts as you already do and point the tokens at them. Set only `--arc-compass-font-family` and the secondary roles use that one face too.

## Copy-paste brand block

Set the tokens once on `:root` and every Experience on the page follows. Fill in your own values:

```css
:root {
  /* Typefaces — load them yourself; the SDK never ships a font. */
  --arc-compass-font-family: "Publico", Georgia, serif;
  --arc-compass-font-family-secondary: "Franklin", Arial, sans-serif;

  /* Accent: recommendation kickers and the Catch Up Card's controls. */
  --arc-compass-accent: #b00020;
  --arc-compass-on-accent: #ffffff; /* text on an accent fill */

  /* Surface pair: the Catch Up Card's full-screen takeover. Opaque. */
  --arc-compass-surface: #ffffff;
  --arc-compass-on-surface: #1a1a1a;

  /* Shape and density. */
  --arc-compass-radius: 8px;
  --arc-compass-card-min-width: 240px;
}
```

Leave `--arc-compass-accent-secondary` out of a new theme: the premium badge takes the primary accent.

### Light and dark recipe

Compass’s colors come from your tokens, not the reader’s OS. Two pairs define the whole palette: `surface`/`on-surface` set the background and ink, and every neutral (muted text, hairlines, subtle fills) is derived from them; `accent`/`on-accent` do the same for filled controls. To support a dark edition, override those four for whatever selector marks it, and set `color-scheme` so native browser chrome (the takeover’s scrollbar) matches:

```css
/* Light — the default edition. */
:root {
  --arc-compass-surface: #ffffff;
  --arc-compass-on-surface: #1a1a1a;
  --arc-compass-accent: #b00020;
  --arc-compass-on-accent: #ffffff;
}

/* Dark — scope to however your site marks its dark edition. */
.night-edition {
  --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 */
}

.night-edition :is(
    arc-compass-recommendations-carousel,
    arc-compass-recommendations-list,
    arc-compass-catchup-card
  ) {
  color-scheme: dark;
}
```

To follow the reader’s OS setting instead of a class, put the dark block inside `@media (prefers-color-scheme: dark) { :root { … } }`.

You do **not** need `color-scheme`, `prefers-color-scheme`, or system colors to make Compass’s own UI dark — the badge, pills, hairlines, muted text, and takeover all derive from the two pairs. In the recommendations Experiences, the dark edition comes from your page: the cards are transparent and draw text in your page’s color, so only the accent needs a dark-friendly value.

> **Caution:**
> 
> If your page background is not the default light, **set the `surface`/`on-surface`pair**. With neither set, the takeover falls back to the light system `Canvas`under your page’s text color, because a covering takeover cannot read your page’s own background. The inline Catch Up Card and the recommendations Experiences are transparent and inherit your page, so only the takeover needs the pair.

### Corner radius

`--arc-compass-radius` is the base every rounded surface derives from, so one brand radius rounds the recommendation cards and the Catch Up Card alike. Set it to **a single CSS length with a unit**. Anything else fails silently: a unitless number (`8`) is not a length, so every corner goes square, and a multi-value radius (`8px 16px`) rounds only the buttons. For square corners throughout, write `0px`.

Buttons take the token as-is, so a pill-sized value (`999px`) gives you pill buttons. Containers — frames, images, rows — follow the token up to `1rem` and hold there, so they stay rectangles however large the value. Pills, dots, and the round carousel arrows are shapes rather than rounding and do not follow it. Rely on the token moving every Experience’s rounding together, not on a given surface getting a given fraction of it — the proportions can be retuned in any release.

### The secondary accent

`--arc-compass-accent-secondary` colors only the premium badge, on the recommendation cards and in the Catch Up Card’s “You might also be interested in” list. It is **deprecated**: when unset, the badge takes `--arc-compass-accent`, so you can drop it from your theme. A site that already sets both accents renders unchanged, and the token keeps working until a future major version removes it.

## What the shadow boundary does and does not protect

**Selectors do not cross a shadow boundary.** A global reset (`* { margin: 0 }`), an aggressive framework rule, an `!important` on a class: none of them can match anything inside an Experience. You do not need to scope your CSS away from the Experiences, and they do not need `!important` to defend against your page.

**Inheritance does cross it.** Inherited properties set on an ancestor flow into the shadow tree, so that is the only real vector. The Experiences reset the inherited properties that can distort a layout rather than restyle it: `line-height`, `letter-spacing`, `word-spacing`, `text-transform`, `text-indent`, `white-space`, `text-align`, `direction`, and `visibility`.

`font-family` and `color` are deliberately **not** reset. Inheriting them is the zero-config story — an Experience should look like the page it sits in — and neither can break geometry. The practical consequence: if your page sets `text-transform: uppercase` on `body`, your own text is uppercase and the Experience’s is not. Use the tokens to theme it.

## Styling internals with `::part()`

Tokens change values Compass anticipated. When you need to restyle an element outright — a different thumbnail aspect ratio, your own type scale — use the part hooks each component page lists:

- [Recommendations Experiences parts](https://dev.arcxp.com/content-recommendations/sdk/recommendations-components/#styling-internals-with-part)
- [Catch Up Card parts](https://dev.arcxp.com/content-recommendations/sdk/catch-up-card/#styling-internals-with-part)

Part names are **versioned public surface**: they will not be removed or moved to a different element within a major version.

Two rules apply to every Experience:

- **`::part()` selects the named element and nothing below it.** `::part(card) img` matches nothing — style `::part(thumbnail)` instead. Every element you can restyle has its own name; if something you need is not in a part table, it is not stylable.
- **A `::part()` declaration beats the Experience’s own rules**, so stick to visual properties. Layout properties such as `display` and `position` on a part can break the component — each component page notes the parts where this matters.

### Setting tokens and parts from Tailwind

Tokens and parts are plain CSS, so they work whatever your styling stack is — you are setting a custom property and matching a `::part()` selector, not reaching for a component API. Two ways to do it from Tailwind:

**A stylesheet block** (clearest for `::part()` rules), colocated with the rest of your CSS:

```css
.rec-rail::part(card) {
  @apply rounded-lg border border-gray-200 bg-white;
}
.rec-rail::part(headline) {
  @apply text-base font-bold;
}
```

**Arbitrary variants**, straight on the tag, when you would rather stay in markup. The tokens are a class; the parts need the `[&::part(...)]` escape hatch because Tailwind has no built-in `part` variant:

```html
<arc-compass-recommendations-list
  class="[--arc-compass-accent:theme(colors.red.600)]
         [&::part(card)]:rounded-lg [&::part(card)]:border
         [&::part(headline)]:font-bold"
>
</arc-compass-recommendations-list>
```

Either reaches the shadow tree — a `::part()` rule is not blocked by the boundary the way a descendant selector is. Utility resets do not leak in either (see [What the shadow boundary does and does not protect](#what-the-shadow-boundary-does-and-does-not-protect)), so you never need to scope Tailwind’s preflight away from the element.

## See also

- [Compass Web SDK: Recommendations Components](https://dev.arcxp.com/content-recommendations/sdk/recommendations-components/) — attributes, card sizing, and part names for the carousel and list.
- [Compass Web SDK: Catch Up Card](https://dev.arcxp.com/content-recommendations/sdk/catch-up-card/) — attributes, events, and part names for the Catch Up Card.
- [Compass Web SDK: Integration Guide](https://dev.arcxp.com/content-recommendations/sdk/integration-guide/) — install, initialize, consent, and identity (required setup).
