Whitelabel shell

Whitelabel shell

The "shell" is what wraps every community-app page: the header, the nav, the footer, the brand tokens. On a Cobuntu customer's custom domain, the shell on yourdomain.com/events (community-app route) needs to match the shell on yourdomain.com/ (the customer's own landing) to-the-tee — same nav items, same fonts, same hover colors, same buttons, same footer. Otherwise a logged-in member moving between the two surfaces feels the chrome swap.

This page covers how that parity works, what's configurable, and which knobs live where.

TL;DR: Edit your shell once in cobuntu-admin → Customize. Both the customer's landing and every community-app page read the same storefrontConfig to render their chrome. New nav item, brand color change, header redesign → propagates to both surfaces on next page-load. No redeploy. No customer-side code.

The big picture

A community-app customer has two consumption modes (see also Two consumption patterns):

  • Headless API — the customer's landing AND every page is rendered by their own infra. They use Cobuntu only as a data backend via REST API. The shell parity question doesn't apply: they own every pixel.

  • Hosted apex — the customer's apex points at Cobuntu's Vercel. Cobuntu serves both:

    • their landing (reverse-proxied from Lovable / Squarespace / Webflow / their own Next.js — whatever they've configured), and
    • the community-app routes (/events, /marketplace, /feed, /atlas, /members, /cart, /checkout, /profile, /conversations, /login, …) rendered by community-app.

    Both are on the same origin (yourdomain.com). The shell parity matters here.

What's in the shell

SurfaceWhat it covers
HeaderLogo, nav items, action icons (search / messages / notifications / cart / account), responsive density
NavThe list of links rendered inside the header (Home / Events / Marketplace / etc.)
FooterCopyright line, social icons, secondary nav
Brand tokensBg / text / brand / button colors, fonts, radii, hover effects

Every one of these comes from one source: the community's storefrontConfig. Edit in cobuntu-admin → Customize, applies everywhere on next page-load.

Header config

In cobuntu-admin → Customize → Header. The community-app reads:

{
  "storefrontConfig": {
    "header": {
      "style": "solid",
      "bgColor": "#0a0a0a",
      "textColor": "#E0E0E0",
      "density": "large",
      "logoUrl": "/community-logos/your-community.png",
      "logoLockup": "wordmark",
      "storeName": "Your Community",
      "navFont": "body",
      "navFontSize": "14px",
      "navFontWeight": "500",
      "navSpacing": "24px",
      "navLetterSpacing": "0.025em",
      "navTextTransform": "none",
      "navLinkStyle": "plain",
      "navLinkPadding": "none",
      "navPosition": "between",
      "navActiveColor": "brandColor",
      "navActiveIndicator": "none",
      "iconChrome": "bordered",
      "signInStyle": "button",
      "showBell": false,
      "borderBottom": true
    }
  }
}

Every field above maps to a real CSS choice in DesktopHeader.tsx / MobileTopBar.tsx. The same JSON is also what your customer's landing template can read (via the auto-theme mechanism — see Theming) to render its own header with the same styling.

The "in sync" promise: when you update header.bgColor in cobuntu-admin, your customer's landing and every community-app page repaint with the new color on the next page-load. No deploy, no cache flush.

Nav config

In cobuntu-admin → Customize → Nav. The community-app reads:

{
  "storefrontConfig": {
    "nav": {
      "style": "solid",
      "items": [
        { "href": "/",            "label": "Home"        },
        { "href": "/about",       "label": "About"       },
        { "href": "/feed",        "label": "Feed",
          "visibleWhen": "approvedMember" },
        { "href": "/events",      "label": "Events"      },
        { "href": "/marketplace", "label": "Marketplace" },
        { "href": "/members",     "label": "Members",
          "visibleWhen": "approvedMember" },
        { "href": "/atlas",       "label": "Map",
          "iconKey": "map", "visibleWhen": "approvedMember" },
        {
          "label": "More",
          "iconKey": "info",
          "children": [
            { "href": "https://yourdomain.com/pricing",
              "label": "Pricing", "iconKey": "briefcase",
              "external": true },
            { "href": "https://yourdomain.com/team",
              "label": "Team", "iconKey": "users", "external": true }
          ]
        }
      ]
    }
  }
}

Item shape

FieldTypeWhat it does
hrefstringURL the item navigates to. Relative for same-origin (/feed) or absolute for off-site (https://...)
labelstringDisplay text
iconKeystringOptional icon name from the shared icon set
externalbooleanWhen true, opens in a new tab
childrenNavItem[]Dropdown menu. Parent renders as a label-only group
visibleWhenenumPer-viewer visibility gate (see below)

visibleWhen — the per-viewer gate

ValueWho sees the item
"always" (default)Everyone (anonymous + authed). Use for marketing pages.
"authenticated"Anyone signed in. Use for "Hub" / "My account".
"approvedMember"Only signed-in users with an ACCEPTED membership in this community. PENDING applicants don't qualify. Use for member-only routes (Feed, Atlas, member directory).

The visibleWhen evaluation lives in both DesktopHeader.tsx (community-app) AND the nav widget used on customer landings — so a logged-out visitor sees the same 4 items on the Lovable landing and on /events, and a member sees the same 7 items on both.

Footer config

Sits in storefrontConfig.pages.<route>.footer or as a default storefrontConfig.footer block. Same model: title, columns, social links, copyright line. Read once per page-load on both surfaces.

Brand tokens

Colors, fonts, radii — all in themeConfig (sibling to storefrontConfig). The widget bundles AUTO-FETCH these on init and apply them as --cobuntu-* CSS variables; the community-app's <WidgetThemeStyle> does the same for its own pages. See Theming for the full mapping.

themeConfig fieldEffect
bgColorPage background
textColorBody text
brandColorPrimary brand (header active indicator, badges, CTA buttons)
linkColorInline links
accentColorSecondary highlight
bodyFont / headingFontType stack
buttonRadius / inputRadius / cardRadiusCorner radius for each surface
primaryBtnBg / primaryBtnTextFilled button styling
secondaryBtnBg / secondaryBtnText / secondaryBtnBorderOutlined button styling

Reading the config programmatically

You can read the whole storefrontConfig for any community via:

GET /api/communities/<communityTag>

Response shape (truncated):

{
  "communityTag": "your-community",
  "name": "Your Community",
  "themeConfig": { /* brand tokens */ },
  "storefrontConfig": {
    "header": { /* … */ },
    "nav":    { /* … */ },
    "modules": { "blog": true, "feed": true, "events": true,
                 "marketplace": true, "members": true, "atlas": true,
                 "home": true },
    "pages":  { /* per-page overrides */ },
    "chromeProxy": { "enabled": false, "headless": true }
  }
}

A second endpoint maps hostnames to communities (used by the auto-theming):

GET /api/communities/by-domain/<hostname>

Same response shape. Use this when you don't know the tag yet — the widget bundles use it on landing pages where the host is yourdomain.com but the tag (your-community) isn't on the page.

What's NOT shared

A handful of things differ deliberately:

SurfaceWhy different
Inline copy on the landing's hero / about / pricing sectionsThese are your landing's content, not Cobuntu's. Cobuntu's community-app routes don't render those sections.
/login, /checkout, /cartCobuntu owns these pages — they need to behave consistently across every customer (security, flow integrity). Brand tokens still apply, but the page structure is Cobuntu's.
/conversations (chat)Full-viewport chat layout — header self-hides to give the chat surface the full screen.

If you want one of these to behave like your landing, that's custom UI work, not a config flip — talk to us if you have a strong case.

Updating safely

When you edit shell config in cobuntu-admin → Customize:

  1. Changes save to the communities.storefrontConfig JSONB column immediately.
  2. Both your landing and every community-app page read fresh on next page-load — there's no CDN cache to bust for the config itself (the /api/communities/<tag> response is short-TTL).
  3. The widget bundles (/widgets/v1/*.js) cache aggressively but read theme on every page-load, so a brand-color change propagates to widgets within seconds of save.
  4. New nav items inherit the existing header styling — no per-item CSS work.

See also