Skip to main content

App Design and UX

Apps should feel native inside Shuuka, not bolted on.

Host Card Style — .shk-host-card

The cleanest way to make an internal container look exactly like the host theme's app card is to add the class shk-host-card to it. The platform injects the full card CSS — background, border, radius, shadow, hover lift, and all theme-defined effects such as spotlight gradients and floor-light animations — directly into your iframe as a <style> tag.

<!-- template.html — any internal card-like container -->
<article class="my-card shk-host-card">
…content…
</article>

You do not write any CSS for visual appearance. The class carries everything.

How it works

When your app initialises (during the new Shuuka() constructor), the SDK sends a shuuka:request_card_style postMessage to the parent page. The active theme responds with a shuuka:card_style message containing a cssText string — the complete compiled .shk-host-card CSS block including :hover, :active, and ::before / ::after pseudo-elements. The SDK injects this as <style id="shk-host-card-style"> into your document head automatically.

This works for all app types including Simple apps (runtime_kind: "simple") which do not call init() — the request is sent automatically at construction time.

This means:

  • Your card inherits the exact same glass/glow/shadow/hover the theme applies to its own app cards.
  • When the profile owner customises card settings in the dashboard, the update is re-broadcast and your card updates live without a reload.
  • You never need to duplicate theme values or query CSS variables manually.

What cssText contains

The theme author controls exactly what CSS is generated. For the built-in Aria theme the injected block looks like this (values reflect the user's dashboard configuration):

.shk-host-card {
background: rgba(255, 248, 230, 0.045);
border: 1px solid rgba(201, 168, 76, 0.13);
border-radius: 18px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.60);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
position: relative;
overflow: hidden;
transition: transform 0.28s ease, box-shadow 0.35s ease, border-color 0.35s ease;
}
.shk-host-card::before {
/* overhead spotlight radial gradient */
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: radial-gradient(ellipse 90% 55% at 50% -5%, rgba(201,168,76,0.10) 0%,);
pointer-events: none;
z-index: 1;
transition: background 0.35s ease;
}
.shk-host-card::after {
/* stage floor-light shimmer line */
content: "";
position: absolute;
bottom: 0; left: 50%;
width: 0; height: 1px;
transform: translateX(-50%);
background: linear-gradient(90deg,gold shimmer…);
z-index: 2;
transition: width 0.50s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.shk-host-card:hover { transform: translateY(-3px);}
.shk-host-card:hover::before { /* intensified spotlight */ }
.shk-host-card:hover::after { width: 85%; }
.shk-host-card:active { transform: translateY(0); }

Other themes produce their own CSS block. Your markup stays the same; only the injected styles change.

Fallback CSS variables

If the theme or SDK does not yet support cssText, four CSS variables are also set on :root inside your iframe as a secondary fallback:

VariableDescription
--shk-host-card-bgcard background
--shk-host-card-radiusborder radius
--shk-host-card-shadowbox shadow
--shk-host-card-borderfull border shorthand

You can reference these in your own CSS for structural defaults while cssText handles the visual layer:

.my-card {
border-radius: var(--shk-host-card-radius, 16px);
background: var(--shk-host-card-bg, transparent);
box-shadow: var(--shk-host-card-shadow, 0 4px 12px rgba(0,0,0,0.05));
border: var(--shk-host-card-border, 1px solid rgba(0,0,0,0.08));
}

Structural rules when using .shk-host-card

The class takes over all visual properties. Your element should:

  • not set its own background, box-shadow, border, or border-radius
  • not set overflow: visible (the class sets overflow: hidden for pseudo-element containment)
  • add position: relative if your content uses absolutely-positioned children with z-index (the class already sets this, so it is inherited automatically)

Host Button Style — .shk-host-btn

The cleanest way to make a button inside your app look exactly like the active theme's primary button is to add the class shk-host-btn. The platform injects the full button CSS — background, color, border, hover lift, active state, disabled state, and all theme-defined effects such as glow and shadow — directly into your iframe as a <style> tag.

<!-- template.html — primary action button -->
<button class="my-submit shk-host-btn" type="button">Submit</button>

<!-- secondary / outlined button -->
<button class="my-cancel shk-host-btn shk-host-btn--secondary" type="button">Cancel</button>

You do not write any CSS for visual appearance. The classes carry everything.

How it works

When your app initialises (during the new Shuuka() constructor), the SDK sends a shuuka:request_button_style postMessage to the parent page. The active theme responds with a shuuka:button_style message containing a cssText string — the complete compiled .shk-host-btn CSS block including :hover, :active, and :disabled pseudo-classes. The SDK injects this as <style id="shk-host-btn-style"> into your document head automatically.

Seven CSS custom properties are also set on :root inside your iframe:

VariableDescription
--shk-btn-primary-bgprimary button background
--shk-btn-primary-colorprimary button text color
--shk-btn-primary-radiusprimary button border-radius
--shk-btn-primary-borderprimary button border shorthand
--shk-btn-secondary-bgsecondary button background
--shk-btn-secondary-colorsecondary button text color
--shk-btn-secondary-bordersecondary button border shorthand

Add shk-host-btn to any <button> or <a> element you want themed. Keep only structural CSS (padding, border-radius, font-size, font-family, width) in your app stylesheet — do not set background, color, or border on the same element.

<button class="my-btn shk-host-btn" type="submit">Enter Giveaway</button>
/* app styles.css — structural only */
.my-btn {
width: 100%;
padding: 11px;
border-radius: 10px;
font-size: 0.9rem;
font-weight: 700;
font-family: inherit;
}

Using CSS variables (for configurable buttons)

If your app lets users configure the button color from the dashboard, keep your own CSS var at the highest priority and fall back to the theme var:

.my-app__button {
background: var(--my-app-btn-color, var(--shk-btn-primary-bg, #111827));
color: var(--my-app-btn-text, var(--shk-btn-primary-color, #ffffff));
}

This gives the profile owner's configuration the final say while still respecting the theme's default when no per-app color is set.

Structural rules when using .shk-host-btn

  • Do not set background, color, or border on the element (the injected style owns these).
  • Do keep layout and size properties in your own CSS (padding, width, border-radius, font-size, font-family).
  • cursor, transition, hover, active, and disabled states are all handled by the injected CSS.

Host Component Style — Typography, Lists, Cards, and Buttons

Beyond cards and buttons, the platform provides exact structural clones of the active theme's typography and list layouts via the shuuka:component_style bridge. These are not convenience helpers. They are the contract native apps are expected to build on.

When requested, the platform injects <style id="shk-host-component-style"> with authoritative !important rules. Theme values coming from createThemeBridge() are meant to win.

Host class checklist

ClassUse CaseOwnership
.shk-host-font-familyRoot app wrapperMakes the entire app inherit the theme font family.
.shk-host-titleApp titles and section headingsTheme owns size, weight, line-height, and color.
.shk-host-subtitleSupporting copy, metadata, helper textTheme owns subdued text styling.
.shk-host-paragraphMain reading copy and primary row textTheme owns the default body style.
.shk-host-labelSmall labels above valuesTheme owns the label treatment.
.shk-host-list-containerVertical list parentOwns row spacing. Do not recreate with local margins.
.shk-host-list-rowIndividual list row or linked rowOwns the shared row surface and hover.
.shk-host-list-gridTheme-owned auto-fill gridsUse when the theme should control grid rhythm.
.shk-host-cardInternal card surfaceOwns card background, border, radius, and shadow.
.shk-host-btnPrimary CTAOwns button visual styling.
.shk-host-btn.shk-host-btn--secondarySecondary or repeated CTAOwns secondary button styling.

Rules

  • Prefer host primitives over app-local visual CSS.
  • Do not recreate list spacing with stacked margins. Use .shk-host-list-container.
  • Do not recreate row surfaces with app-local background, border, border-radius, or hover states. Use .shk-host-list-row.
  • Do not recreate theme card visuals inside the app. Use .shk-host-card.
  • If the app allows the profile owner to configure typography directly, use .shk-host-font-family and app-local sizing instead of forcing .shk-host-title, .shk-host-subtitle, or .shk-host-paragraph.

Example: Theme-perfect list markup

<div class="shk-host-font-family">
<div class="shk-host-list-container" role="list">
<a href="https://example.com" class="shk-host-list-row" role="listitem" target="_blank" rel="noopener noreferrer">
<img src="icon.png" alt="" style="width:56px;height:56px;border-radius:8px;object-fit:cover;flex-shrink:0;" />
<div style="flex:1;min-width:0;">
<span class="shk-host-label">Platform</span>
<p class="shk-host-paragraph" style="margin:0;">Instagram</p>
</div>
</a>
</div>
</div>

createThemeBridge() values that style host classes

createThemeBridge({
component: {
titleColor: '#111827',
subtitleColor: '#6b7280',
paragraphColor: '#111827',
titleFontSize: '1rem',
subtitleFontSize: '0.875rem',
paragraphFontSize: '1rem',
titleLineHeight: '1.3',
subtitleLineHeight: '1.5',
paragraphLineHeight: '1.6',
titleFontWeight: 700,
subtitleFontWeight: 400,
paragraphFontWeight: 400,
subtitleOpacity: 0.8,
rowBg: 'rgba(0,0,0,0.03)',
rowBorder: 'rgba(0,0,0,0.08)',
rowHoverBg: 'rgba(0,0,0,0.05)',
rowHoverBorder: 'rgba(0,0,0,0.12)',
},
});

That component object drives:

  • .shk-host-title
  • .shk-host-subtitle
  • .shk-host-paragraph
  • .shk-host-label
  • .shk-host-list-container
  • .shk-host-list-row
  • .shk-host-list-grid

If your app is still compensating with local CSS after that, it is probably not using the host contract correctly.


Theme-Aware Card Variables (legacy fallback)

If you are not using .shk-host-card, you can still reference individual CSS variables that the platform injects for theme-aware defaults:

VariableUse
--app-card-bgcard background
--app-card-borderborder color
--app-card-accentCTA background
--app-card-accent-textCTA text
--app-card-radiuscard radius
--app-card-shadowcard shadow

Adapting to Dark or Light Themes (Color Schemes)

When the profile owner uses a theme that defines "color_scheme": "dark", the platform passes this to every installed app.

  1. HTML injection: The platform automatically injects <html class="dark"> into the app's document.
  2. URL Parameter: The iframe URL includes &theme_mode=dark.

Use this to ensure your text stays legible on dark theme surfaces. If you use Tailwind, you can use standard dark:text-white classes. If you use standard CSS, you can scope rules like .dark p { color: #fff; }.

Note: The platform will NOT forcefully override your app's text color. It is entirely up to you (the app developer) to react to the .dark class and pick appropriate contrast colors for your typography and embedded elements.

Ownership Rule

Only one layer should visually own the card.

PatternVerdict
Theme owns the outer card, app renders content insideGood
App intentionally renders its own full surface and disables outer stylingGood
Theme card + inner app card both render full shadows/backgroundsWrong

Bottom Sheet Contract

If your app supports Bottom Sheet mode:

  • let the host own the sheet chrome
  • do not render a second close shell inside the app
  • send the required bottom-sheet messages
  • test success, close, back, and resize states

Resize Contract

Apps must behave correctly when embedded:

  • do not assume fixed height
  • avoid clipping your own content
  • let the platform resize contract manage outer containers

Vanilla JS vs React

ApproachBest for
Vanilla JSsmall cards and minimal interactions
Reactlarger app shells, richer admin tabs, or public pages with more state

If you ship React, commit the built output. Shuuka does not build your bundle at runtime.

UX Checklist

  • handle loading, empty, error, success, and disabled states
  • escape user-provided strings before innerHTML
  • scope DOM access to your app root
  • keep auth-only actions inside admin pages