Skip to main content

Theme App-Card Integration

Themes are responsible for presenting installed apps cleanly.

Visual Ownership Rule​

In custom SDK themes, visual styling belongs on the outer app card wrapper, not on the iframe container.

ElementResponsibility
.shk-theme__app-cardvisual surface: background, border, radius, shadow
.billboard-app-containerclip mask and sizing only
iframe bodyapp content only

Do / Don’t​

DoDon’t
style .shk-theme__app-cardput shadows on .billboard-app-container
use --app-card-* variablesduplicate card chrome inside multiple layers
let the theme own cosmetic defaultsoverride every app visually by brute force

Why This Matters​

If both the theme wrapper and the iframe container own shadows or backgrounds, users see a double-card effect. That breaks the visual system immediately.

Minimal Example​

.shk-theme__app-card {
background: var(--app-card-bg, var(--shk-default-card-bg, #ffffff));
border-radius: var(--app-card-radius, 14px);
box-shadow: var(--app-card-shadow, 0 1px 4px rgba(0, 0, 0, 0.06));
overflow: visible;
}

.shk-theme__app-card .billboard-app-container {
background: transparent;
border: none;
box-shadow: none;
}

Broadcasting Your Card Style to App Iframes​

Apps can opt into inheriting your card's exact visual style — background, border, radius, shadow, hover lift, etc. — by relying on the Platform SDK bridge.

The Shuuka SDK provides a createThemeBridge utility that completely handles the iframe postMessage protocol, live dashboard CSS variable syncing, and fallback resolution automatically.

Integrating the Theme Bridge​

Your only responsibility in src/index.js is to provide your compiled SCSS defaults. This ensures apps always receive valid sizes and colours even before the dashboard makes live style modifications over the bridge.

import { createThemeBridge } from '@shuuka';

createThemeBridge({
card: {
backgroundColor: 'rgba(255, 255, 255, 0.06)', // compiled SCSS fallback
borderColor: 'rgba(255, 255, 255, 0.12)',
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '16px',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.18)',
backdropFilter: 'blur(12px)', // Arbitrary CSS works natively
},
btn: {
primary: {
backgroundColor: '#222222', // light mode default
color: '#ffffff',
borderRadius: '10px',
border: 'none',
},
secondary: {
backgroundColor: '#eaeaea', // light mode default
color: '#000000',
border: 'none',
}
}
});

backdropFilter default​

If you omit backdropFilter, the SDK applies backdrop-filter: blur(12px) automatically. This means all themes get a glass blur by default. For solid-card or flat themes, explicitly pass backdropFilter: 'none' to opt out:

createThemeBridge({
card: {
backgroundColor: '#1a1a1a',
borderRadius: '16px',
backdropFilter: 'none', // disable the default blur
},
});

Pseudo-selectors and hover states​

You can pass pseudo-selector keys directly inside card as objects. The SDK renders them as separate CSS rules:

createThemeBridge({
card: {
borderRadius: '18px',
boxShadow: '0 8px 32px rgba(0,0,0,0.60)',
'&:hover': {
transform: 'translateY(-3px)',
borderColor: 'rgba(255,255,255,0.28)',
},
'&:active': { transform: 'translateY(0)' },
'&::before': {
content: '""',
position: 'absolute',
inset: 0,
background: 'radial-gradient(ellipse at 50% -5%, rgba(255,255,255,0.08) 0%, transparent 70%)',
pointerEvents: 'none',
borderRadius: 'inherit',
},
},
});

component Parameter​

The component parameter styles platform UI elements that apps use inside their iframes — titles, subtitles, list rows, paragraphs, and labels. Apps that use .shk-host-title, .shk-host-subtitle, .shk-host-list-container, etc. will inherit these styles automatically.

createThemeBridge({
card: { /* ... */ },
btn: { /* ... */ },
component: {
title: {
color: '#e8dfc8',
},
subtitle: {
color: 'rgba(232, 223, 200, 0.52)',
opacity: '0.8',
},
row: {
backgroundColor: 'rgba(255, 255, 255, 0.03)',
borderColor: 'rgba(255, 255, 255, 0.08)',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.07)',
borderColor: 'rgba(255, 255, 255, 0.18)',
},
},
},
});
KeyTargetsAccepts
title.shk-host-titlecolor, any CSS property
subtitle.shk-host-subtitlecolor, opacity, any CSS property
row.shk-host-list-container rowsbackgroundColor, borderColor, &:hover object
paragraphparagraph text blockscolor, any CSS property
labellabel text (uppercase)color, any CSS property

If you omit component, the SDK applies neutral defaults (color: inherit, background: transparent).


For themes with dark mode, pass dark-aware values that reflect your compiled SCSS defaults:

const isDark = document.documentElement.classList.contains('dark');

createThemeBridge({
card: { /* ... */ },
btn: {
primary: {
backgroundColor: isDark ? '#e8e8e8' : '#222222',
color: isDark ? '#111111' : '#ffffff',
borderRadius: '10px',
border: 'none',
},
secondary: {
backgroundColor: isDark ? 'rgba(255,255,255,0.10)' : '#eaeaea',
color: isDark ? 'rgba(255,255,255,0.85)' : '#000000',
border: 'none',
}
}
});

That's it.

When the user changes card settings in the dashboard, the SDK intercepts the configuration, sets the --app-card-* CSS variables on the root <html> element dynamically, rebuilds the internal stylesheet, and broadcasts that updated CSS safely to every app iframe running on the page.


Dark Mode and Transparent App Iframes​

How the platform manages transparency (platform-owned — no theme action required)​

When your theme is dark, the platform passes ?transparent=1&theme_mode=dark to every app sub-iframe. The platform then automatically injects a <style data-platform-canvas-guard> tag as the very first element inside each iframe's <head>, before any other CSS or script parses.

/* Injected by the platform — you will see this in DevTools */
html, body { background: transparent !important; }
body { color-scheme: dark !important; }

You do not need to do anything. This is entirely platform-managed.

Why this guard exists​

There is a CSS spec behavior (Color Adjust L3) that affects transparent iframes in dark mode:

When :root's computed background-color is transparent, the browser paints the viewport canvas — the layer below the DOM — with the UA system Canvas color. When color-scheme: dark is active on :root (via <meta> or CSS), that Canvas color is the system dark background (#1c1c1e on macOS Chrome).

Result: even with html, body { background: transparent }, the dark canvas shows through the transparent iframe body, producing a solid dark background on the app card.

The guard prevents this by:

  1. Setting html and body background to var(--shk-host-card-bg, transparent) synchronously before UA styles apply:
    • Solid-card dark themes (e.g. shuuka-dark with rgba(28,39,59,0.92)) fill the iframe body with the theme's card color, preventing the white UA light-mode canvas from showing through.
    • Glass themes (e.g. Aria, Nebula with near-zero opacity) remain effectively transparent, preserving the frosted-glass compositing effect.
    • The transparent fallback applies to themes that don't supply a card background.
  2. Scoping color-scheme: dark to body only — this keeps UA form elements and scrollbars dark without darkening the canvas through :root
  3. The platform also suppresses the <meta name="color-scheme" content="dark"> tag for transparent dark iframes (server-side), since the meta has the identical canvas-darkening effect

What theme developers must not do in app sub-iframe CSS​

Don'tWhy
Set color-scheme on :root or html in app CSSFights the platform guard and re-darkens the canvas
Set an explicit background-color (other than transparent) on :root in app CSSOverrides the transparent lock
Add <meta name="color-scheme" content="dark"> manually in app HTMLThe platform suppresses the meta server-side; adding it back undoes that

html.dark class selectors in your theme SCSS and in app CSS are safe — they target content inside <body> and do not affect the canvas.

Debugging​

Open DevTools inside any app sub-iframe on a dark theme. The first tag in <head> should be:

<style data-platform-canvas-guard>html,body{background:transparent!important;}body{color-scheme:dark!important;}</style>

If it is missing, the iframe was not served with ?transparent=1 — check that your theme has meta.color_scheme: dark in theme.config.json and that templateUsesWrapperStyles is true for your theme type.


App Manifest Interaction​

Apps may declare card_settings that intentionally lock some card behavior. Theme authors should assume:

  • structural app constraints may win
  • cosmetic defaults should still come from the theme whenever possible

card_settings Keys Themes Should Respect​

KeyPossible valuesTheme impact
card_styletrue, falseWhether the app expects Shuuka card chrome
styletrue, falseLegacy alias for card_style
backgroundCSS color, gradient, or transparentApp locks the card background. Omit to inherit the platform's theme-aware default (light/dark mode).
border_radiusCSS length such as 0, 12px, 999pxApp locks the outer card radius
box_shadowtrue, false, or CSS shadow valueApp locks the outer card shadow behavior. Omit to inherit the platform default.
full_widthtrue, falseWhether the app should stretch edge-to-edge
disable_scroll_overlaytrue, falseHost compatibility flag for overlay behavior
size.width / size.heightpixel value, "100%", "auto"Embed sizing hints

If an app declares background, border_radius, or box_shadow, treat those as explicit app-level visual locks and avoid reintroducing a second conflicting card layer in the theme.

When a visitor has not consented to an app's third-party data transfers, the Shuuka platform renders a .shk-consent-placeholder element inside the app card in place of the iframe content.

This element is owned entirely by the platform — not the theme.

ElementOwnerWhat to do
.shk-theme__app-cardThemeStyle freely with card tokens
.shk-consent-placeholderPlatformDo not style. The platform injects a self-contained white card with fixed brand styling.

The placeholder renders a white card (with shadow and border-radius) that is always readable regardless of the theme's background color. Text and button labels are supplied by the platform in the visitor's active locale — themes have no control over this content.

Do not do this​

/* WRONG — consent placeholder is platform-owned */
.shk-consent-placeholder {
color: var(--theme-text-color);
background: var(--app-card-bg);
}

What the platform renders​

  • White self-contained card with box-shadow
  • Red icon for disabled state, green icon for pending state
  • Title, description, and action button in the visitor's locale
  • Translations provided automatically by the platform — no theme action required

The theme card wrapper (.shk-theme__app-card) still applies its own background, border, and radius around the placeholder, so the overall card frame continues to match the theme.