Web SDKTheming

Theming

Every element renders inside a shadow root styled from --dc-* CSS custom properties. That isolation runs both ways: your page’s CSS cannot reach into the widget, and the widget’s cannot leak out. Restyling with your own selectors will not work — set the tokens instead.

Tokens

Eight always render:

TokenCSS propertyDefault
accent--dc-accent#0e1726
accentForeground--dc-accent-fg#ffffff
surface--dc-surface#ffffff
ink--dc-ink#0e1726
inkMuted--dc-ink-mutedrgba(14, 23, 38, 0.6)
border--dc-borderrgba(0, 0, 0, 0.10)
radius--dc-radiussoft
density--dc-leadingcomfortable

radius takes sharp (4px), soft (12px), pill (9999px), or a plain number of pixels. density takes comfortable (line-height 1.5) or compact (1.35).

Two more render only when you set them:

TokenCSS property
fontFamily--dc-font-family
fontDisplay--dc-font-display

And three are runtime signals rather than CSS variables: fontUrl, locale, currency.

The default accent is a near-black navy, not a blue. If you set nothing, the widget looks deliberately neutral rather than branded.

Setting a theme

DriveCars.init({
  session,
  onRefreshToken,
  theme: {
    accent: '#ff6b00',
    accentForeground: '#ffffff',
    radius: 'pill',
    density: 'compact',
  },
});

Or for one element only:

DriveCars.mount('search', el, { theme: { accent: '#0055aa' } });

Precedence

Most specific wins:

mount({ theme })  >  init({ theme })  >  session theme_overrides  >  DriveCars default

theme_overrides comes from your partner record, so we can set your brand colours once and every embed picks them up with no code from you at all. Ask us to set them rather than repeating a palette across pages.

Each tier merges token by token. Overriding accent alone leaves the other seven where the tier below put them — you never have to restate a whole theme to change one value.

Fonts

fontFamily and fontDisplay are ordinary CSS font stacks. They style text but do not load anything.

fontUrl loads a stylesheet — typically a Google Fonts or self-hosted CSS URL — and is injected as a <link> once per document, shared across every element on the page rather than fetched per element.

theme: {
  fontUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap',
  fontFamily: '"Inter", system-ui, sans-serif',
}

fontUrl must be an https: URL. Anything else — http:, a javascript: URL, a protocol-relative path — is refused with a console error and simply not injected. Set both fontUrl and fontFamily: the first loads the file, the second is what actually applies it.

Ordering, and why it does not bite you

Elements resolve their theme once, when they first connect, and bake it into their shadow root. In the integration you will actually write, the loader injects the element bundle the moment it sees the tag — so that bake usually happens while your session fetch is still in flight, before init() has run.

The SDK corrects for this: init() re-applies the resolved theme to every <drivecars-*> already in the document as inline custom properties, which outrank the shadow root’s own rule. An element that upgrades later resolves the same tiers itself. Both orderings end in the same place, so you do not have to sequence anything.

This is why theme survives an element that mounted before init() even though the product selection does not — see Web SDK. Keep the elements in your HTML and call init() afterwards regardless.

Right-to-left

Set lang="ar" (on the element, or init({ lang: 'ar' })) and the widget renders right-to-left. This is a locale setting, not a theme token — there is no direction token to set.

Check your own container CSS under RTL: the widget flips its own layout, but a fixed margin-left on your wrapper will not.