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.
| Element | Responsibility |
|---|---|
.shk-theme__app-card | visual surface: background, border, radius, shadow |
.billboard-app-container | clip mask and sizing only |
| iframe body | app content only |
Do / Don’t
| Do | Don’t |
|---|---|
style .shk-theme__app-card | put shadows on .billboard-app-container |
use --app-card-* variables | duplicate card chrome inside multiple layers |
| let the theme own cosmetic defaults | override 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)',
},
},
},
});
| Key | Targets | Accepts |
|---|---|---|
title | .shk-host-title | color, any CSS property |
subtitle | .shk-host-subtitle | color, opacity, any CSS property |
row | .shk-host-list-container rows | backgroundColor, borderColor, &:hover object |
paragraph | paragraph text blocks | color, any CSS property |
label | label 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 computedbackground-coloristransparent, the browser paints the viewport canvas — the layer below the DOM — with the UA systemCanvascolor. Whencolor-scheme: darkis active on:root(via<meta>or CSS), that Canvas color is the system dark background (#1c1c1eon 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:
- Setting
htmlandbodybackground tovar(--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
transparentfallback applies to themes that don't supply a card background.
- Solid-card dark themes (e.g. shuuka-dark with
- Scoping
color-scheme: darktobodyonly — this keeps UA form elements and scrollbars dark without darkening the canvas through:root - 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't | Why |
|---|---|
Set color-scheme on :root or html in app CSS | Fights the platform guard and re-darkens the canvas |
Set an explicit background-color (other than transparent) on :root in app CSS | Overrides the transparent lock |
Add <meta name="color-scheme" content="dark"> manually in app HTML | The 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
| Key | Possible values | Theme impact |
|---|---|---|
card_style | true, false | Whether the app expects Shuuka card chrome |
style | true, false | Legacy alias for card_style |
background | CSS color, gradient, or transparent | App locks the card background. Omit to inherit the platform's theme-aware default (light/dark mode). |
border_radius | CSS length such as 0, 12px, 999px | App locks the outer card radius |
box_shadow | true, false, or CSS shadow value | App locks the outer card shadow behavior. Omit to inherit the platform default. |
full_width | true, false | Whether the app should stretch edge-to-edge |
disable_scroll_overlay | true, false | Host compatibility flag for overlay behavior |
size.width / size.height | pixel 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.
GDPR Consent Placeholder
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.
| Element | Owner | What to do |
|---|---|---|
.shk-theme__app-card | Theme | Style freely with card tokens |
.shk-consent-placeholder | Platform | Do 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.