Skip to main content
Unveilydocs

Navigation

Control the bottom tab bar, side drawer, top dropdown menu, and bottom sheet from the web.

At a Glance

The Navigation Bridge lets you control all native navigation panels directly from your web page.

ComponentBridge namespaceconfig.json key
Bottom TabsunveilyBridge.bottomTabsmodules.bottomTabs.enabled
Side DrawerunveilyBridge.sideDrawermodules.sideDrawer.enabled
Top Down MenuunveilyBridge.topDownMenumodules.topDownMenu.enabled
Bottom SheetunveilyBridge.bottomSheetmodules.bottomSheet.enabled

Panel Exclusivity

Bottom Sheet, Top Down Menu, and Side Drawer can only be open one at a time. Opening a new panel automatically closes any other open panel.


Timing — Wait for unveilyGlueReady

window.unveilyBridge is registered before the page loads, but the module namespaces (bottomTabs, sideDrawer, etc.) are only attached after onPageFinished fires on the native side.

In SPAs (React, Vue, etc.) useEffect or onMounted may run before the glue is injected, causing calls like setConfig() to silently do nothing.

Always listen for the unveilyGlueReady event before calling any bridge method on mount:

window.addEventListener('unveilyGlueReady', () => {
  window.unveilyBridge.bottomTabs.setConfig({ items: [...] });
  window.unveilyBridge.sideDrawer.setConfig({ items: [...] });
}, { once: true });

For user-triggered actions (button clicks, etc.) this is not needed — the glue is always ready by the time a user can interact.


Enable Modules

The distributed config.json ships with every UI module disabled (opt-in). Turn on only the panels your app actually uses.

Enable only what you use

Modules are off by default so unrequested tabs, drawers, and gesture observers aren't injected into your app — and so you avoid pulling in unnecessary permissions or store-review complications. Flip only the components you use to "enabled": true.

assets/config.json
{
  "modules": {
    "bottomTabs":  { "enabled": true, "autoHide": false, "heightDp": 60 },
    "sideDrawer":  { "enabled": true },
    "topDownMenu": { "enabled": true },
    "bottomSheet": { "enabled": true }
  }
}

To explore quickly, a demo config with every panel enabled ships as config.sample.json. Back up config.json, swap it in, and restore when done. (Android: app/src/main/assets/, iOS: Resources/)

cp config.json config.json.bak && cp config.sample.json config.json

Don't judge tier (plan) from local config

Plan gating applies only on the remote/license path. Local config.json bypasses it, so a local demo can show panels your actual plan would block. Verify production availability against your plan.


Panel Color Customization

Set a panelColor field on any module to apply your brand color to that panel's background. The value is stored in config.json and survives SDK updates without any MainActivity.kt changes.

Single color (applied to both light and dark mode):

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "panelColor": "#CC1A1A2E"
    }
  }
}

Separate light/dark colors (automatically switches with the system theme):

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "panelColor": {
        "light": "#CCF0F0F5",
        "dark":  "#CC1A1A2E"
      }
    },
    "sideDrawer": {
      "enabled": true,
      "panelColor": {
        "light": "#CCFFFFFF",
        "dark":  "#CC2C2C2C"
      }
    },
    "topDownMenu": {
      "enabled": true,
      "panelColor": "#CC101020"
    },
    "bottomSheet": {
      "enabled": true,
      "panelColor": "#CC1C1C1E"
    }
  }
}

Color format: "#AARRGGBB" (with alpha) or "#RRGGBB" (fully opaque). Omit panelColor to use the default SDK background.

Priority

panelColor takes priority over the High Contrast setting from the accessibility panel. If you need to support High Contrast mode alongside brand colors, either leave panelColor unset or adjust colors dynamically from JS when the accessibility settings change.

Icon / Label Colors

Use iconColor and labelColor to tint icons and set label text color independently. Supported on Bottom Tabs, Top Down Menu, and Side Drawer. Same single-string or {light, dark} object format as panelColor.

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "panelColor":  "#CC1A1A2E",
      "iconColor":   { "light": "#1A1A2E", "dark": "#FFFFFF" },
      "labelColor":  { "light": "#1A1A2E", "dark": "#FFFFFF" },
      "activeAlpha":   1.0,
      "inactiveAlpha": 0.5
    },
    "sideDrawer": {
      "enabled": true,
      "panelColor": "#CC2C2C2C",
      "iconColor":  "#FFFFFF",
      "labelColor": "#FFFFFF"
    },
    "topDownMenu": {
      "enabled": true,
      "panelColor": "#CC101020",
      "iconColor":  "#FFFFFF",
      "labelColor": "#CCCCCC"
    }
  }
}
OptionApplies toDefault
iconColorIcon ImageView tint (SRC_IN blend)Original image colors
labelColorLabel TextView text color#FFFFFF (white)
activeAlphaActive tab opacity (Bottom Tabs only)1.0
inactiveAlphaInactive tab opacity (Bottom Tabs only)0.6

Icon tinting

iconColor works best with single-color icons (SVG or flat PNG). Applying it to multi-color icons will flatten them to a single hue.

Active Tab Indicator

Use activeIndicator to add a visual marker to the currently active tab. If activeIndicatorColor is not set, iconColor is used as the fallback.

"bottomTabs": {
  "enabled": true,
  "panelColor": "#E8101020",
  "iconColor":  { "light": "#1F2937", "dark": "#FFFFFF" },
  "activeIndicator":      "dot",
  "activeIndicatorColor": { "light": "#2563EB", "dark": "#60A5FA" }
}
OptionDescriptionDefault
activeIndicatorIndicator style — "none" / "dot" / "pill" / "bar""none"
activeIndicatorColorIndicator color (same single/object format as panelColor)#6366F1 / #818CF8 (Indigo)
activeIndicatorWidth"bar" width — "short" (20dp centered) / "full" (full tab width)"short"
activeIndicatorPosition"bar" position — "top" (above icon) / "bottom" (below label)"top"
  • dot — small filled circle below the label
  • bar — short horizontal line above the icon
  • pill — rounded-rect background wrapping the icon and label

Pill opacity

For "pill" style, include an alpha channel in activeIndicatorColor (e.g. "#332563EB" ≈ 20% opacity). A fully opaque color will obscure the icon and label.


Bottom Tabs

A tab bar fixed at the bottom of the screen.

Tab Bar Height

Use the heightDp option in config.json to adjust the tab bar height.

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "heightDp": 64
    }
  }
}
OptionTypeDefaultDescription
heightDpnumber54Tab bar height in dp. Automatically clamped to the 44–96 range.

Accessibility minimum

Values below 44dp are raised to 44dp (WCAG 2.5.5 minimum touch target). Values above 96dp are clamped to 96dp.

show / hide / toggle

window.unveilyBridge.bottomTabs.show();
window.unveilyBridge.bottomTabs.hide();
window.unveilyBridge.bottomTabs.toggle();

setConfig

window.unveilyBridge.bottomTabs.setConfig({
  visible: false,  // optional — default: true. Pass false to keep the bar hidden on first load.
  items: [
    { id: "home",   label: "Home",   icon: "home.svg",   url: "/home" },
    { id: "search", label: "Search", icon: "search.svg", url: "/search" },
    { id: "my",     label: "My",     icon: "person.svg", url: "/my" }
  ]
});

The tab bar is shown automatically on the first setConfig() call. Pass visible: false to keep it hidden and call show() manually when ready. Subsequent setConfig() calls do not change visibility.

Icons: Place icon files in assets/menu_icons/ and reference them by filename. Both .svg and raster formats (.png, .webp, etc.) are supported. Local .svg files are rendered via AndroidSVG for crisp display at any density; other formats use Glide.

setBadge

Show a notification badge on a specific tab. Pass 0 to remove.

window.unveilyBridge.bottomTabs.setBadge("home", 5);  // show badge
window.unveilyBridge.bottomTabs.setBadge("home", 0);  // remove badge

Callback

function onBottomTabClick(result) {
  console.log(result.id);   // tab id
  console.log(result.url);  // url configured for the tab
}

Side Drawer

A navigation list that slides in from the side of the screen.

open / close / toggle

window.unveilyBridge.sideDrawer.open();
window.unveilyBridge.sideDrawer.close();
window.unveilyBridge.sideDrawer.toggle();

setConfig

window.unveilyBridge.sideDrawer.setConfig({
  widthDp: 320,   // optional — drawer width in dp (default: 320)
  items: [
    { id: "profile",  label: { ko: "프로필",  en: "Profile",  ja: "プロフィール" }, icon: "person.png",   url: "/profile" },
    { id: "settings", label: { ko: "설정",    en: "Settings", ja: "設定"         }, icon: "settings.png", url: "/settings" },
    { id: "refresh",  type: "button", label: { ko: "새로고침", en: "Refresh", ja: "更新" } },
    { id: "lang",     type: "select", value: "en", options: [
        { value: "ko", label: { ko: "한국어", en: "Korean",   ja: "韓国語" } },
        { value: "en", label: { ko: "영어",   en: "English",  ja: "英語"   } },
        { value: "ja", label: { ko: "일본어", en: "Japanese", ja: "日本語" } }
    ]}
  ]
});

setItemEnabled / setItemVisible

window.unveilyBridge.sideDrawer.setItemEnabled("settings", false);
window.unveilyBridge.sideDrawer.setItemVisible("settings", false);

setBadge

Show a notification badge on an icon-type item. Pass 0 to remove.

window.unveilyBridge.sideDrawer.setBadge("profile", 3);
window.unveilyBridge.sideDrawer.setBadge("profile", 0); // remove

Callbacks

// icon-type item tapped (backward compatible)
function onSideDrawerItemClick(result) {
  console.log(result.id);   // item id
  console.log(result.url);  // url of the tapped item
}

// button or select item interacted with
function onSideDrawerAction(result) {
  console.log(result.id);     // item id
  console.log(result.type);   // "button" | "select"
  console.log(result.value);  // "" for button; selected option value for select
}

Top Down Menu

A grid menu that slides down from the top of the screen.

open / close / toggle

window.unveilyBridge.topDownMenu.open();
window.unveilyBridge.topDownMenu.close();
window.unveilyBridge.topDownMenu.toggle();

setConfig

window.unveilyBridge.topDownMenu.setConfig({
  columns: 4,              // grid columns (1–5, default: 4)
  maxHeightFraction: 0.5,  // maximum height fraction — a cap (0.1–1.0, default: 0.5). Shows shorter than the cap with little content
  items: [
    { id: "home",     label: { ko: "홈",    en: "Home",    ja: "ホーム" }, icon: "home.png" },
    { id: "search",   label: { ko: "검색",  en: "Search",  ja: "検索"   }, icon: "search.png" },
    { id: "refresh",  type: "button", label: { ko: "새로고침", en: "Refresh", ja: "更新" } },
    { id: "lang",     type: "select", value: "en", options: [
        { value: "ko", label: { ko: "한국어", en: "Korean",   ja: "韓国語" } },
        { value: "en", label: { ko: "영어",   en: "English",  ja: "英語"   } },
        { value: "ja", label: { ko: "일본어", en: "Japanese", ja: "日本語" } }
    ]},
    { id: "banner",   type: "image",  icon: "promo.svg", heightDp: 80 }
  ]
});

Column behavior

columns applies only to type:'icon' grid cells. button/select/toggle/image/header/separator items are always full-width rows. columns is also a fixed value — on tablets the column count does not auto-increase; each column gets wider instead. To use more columns on tablets, branch on app.isTablet().

var cols = window.unveilyBridge.app.isTablet() ? 5 : 3;
window.unveilyBridge.topDownMenu.setConfig({ columns: cols, items: [ /* ... */ ] });

setItemEnabled / setItemVisible

window.unveilyBridge.topDownMenu.setItemEnabled("search", false);
window.unveilyBridge.topDownMenu.setItemVisible("search", false);

Dismiss gestures

  • Swipe up: A fast upward swipe while the menu is open closes it.
  • Back button: Closes the menu instead of exiting the app.

Callbacks

// icon-type item tapped (backward compatible)
function onTopDownMenuItemClick(result) {
  console.log(result.id);   // item id
  console.log(result.url);  // url of the tapped item
}

// button or select item interacted with
function onTopDownMenuAction(result) {
  console.log(result.id);     // item id
  console.log(result.type);   // "button" | "select"
  console.log(result.value);  // "" for button; selected option value for select
}

Bottom Sheet

A panel that slides up from the bottom of the screen. Compatible with the Bottom Tab Bar.

show

window.unveilyBridge.bottomSheet.show({
  title: "Product Details",  // optional
  content: "Description",    // optional
  heightFraction: 0.5,       // fraction of screen height (0.1–1.0, default: 0.5)
  heightDp: 400              // fixed height in dp — takes priority over heightFraction
});

hide / toggle

window.unveilyBridge.bottomSheet.hide();
window.unveilyBridge.bottomSheet.toggle();

setConfig

Set the sheet height with setConfig. If the sheet is open it applies immediately (live resize); if closed it applies on the next show().

// Closed — height applies on the next show()
window.unveilyBridge.bottomSheet.setConfig({ heightFraction: 0.7 });

// Called while open — immediate live resize
window.unveilyBridge.bottomSheet.setConfig({ heightFraction: 0.3 });

Unified panel sizing rule

For all three panels (top-down menu, side drawer, bottom sheet) sizing is done through setConfig. Called while open it applies immediately; while closed it applies on the next open()/show(). open()/show() only display the panel — calling them again on an already-open panel does not change its size.

Size options: top-down menu maxHeightFraction (a cap) · side drawer widthDp (fixed width) · bottom sheet heightFraction or heightDp (fixed height, heightDp takes precedence).

Dismiss gestures

  • Swipe down: A fast downward swipe closes the sheet.
  • Back button: Closes the sheet instead of exiting the app.
  • Tap outside: Tapping the scrim above the sheet closes it.

Item Types

Both Top Down Menu and Side Drawer items support a type field that controls how the item is rendered.

typeRenderingCloses panel on interactionCallback
"icon"Icon + label grid/list cell (default)YesonTopDownMenuItemClick / onSideDrawerItemClick
"image"Full-width image row (SVG recommended)Only if url setonTopDownMenuItemClick / onSideDrawerItemClick
"button"Full-width native ButtonNoonTopDownMenuAction / onSideDrawerAction
"select"Full-width native Spinner (dropdown)NoonTopDownMenuAction / onSideDrawerAction
"toggle"Full-width Switch (label + on/off)NoonTopDownMenuAction / onSideDrawerAction
"separator"Thin horizontal divider lineNone
"header"Non-interactive section title labelNone

"button", "select", and "toggle" do not auto-close the panel on interaction — your JS callback decides what to do.

type: "icon" (default)

{
  "id": "settings",
  "label": { "ko": "설정", "en": "Settings", "ja": "設定" },
  "icon": "settings.png",
  "url": "/settings"
}

label can also be a plain string for single-language setups: "label": "Settings"

type: "image"

Full-width image row. Local .svg files are rendered via AndroidSVG; other formats use Glide.

{
  "id": "banner",
  "type": "image",
  "icon": "promo.svg",
  "heightDp": 80,
  "url": "/promo"
}
FieldTypeDefaultDescription
iconstringAsset filename (e.g. "banner.svg") or remote URL
heightDpnumber80Row height in dp (20–400)
urlstringOptional. If set, tapping fires onTopDownMenuItemClick and closes the panel

Place the SVG file in assets/menu_icons/ and reference it by filename.

type: "button"

Full-width native Button. Clicking fires onTopDownMenuAction / onSideDrawerAction.

{
  "id": "refresh",
  "type": "button",
  "label": { "ko": "새로고침", "en": "Refresh", "ja": "更新" }
}

type: "select"

Full-width native Spinner (dropdown). Selecting an option fires onTopDownMenuAction / onSideDrawerAction.

{
  "id": "lang",
  "type": "select",
  "value": "en",
  "options": [
    { "value": "ko", "label": { "ko": "한국어", "en": "Korean",   "ja": "韓国語"  } },
    { "value": "en", "label": { "ko": "영어",   "en": "English",  "ja": "英語"    } },
    { "value": "ja", "label": { "ko": "일본어", "en": "Japanese", "ja": "日本語"  } }
  ]
}
FieldTypeDefaultDescription
valuestringPre-selected option value
optionsarrayArray of { value, label } objects

type: "toggle"

Full-width Switch with label on the left and the toggle on the right. Fires onTopDownMenuAction / onSideDrawerAction with value: "true" or "false".

{
  "id": "dark",
  "type": "toggle",
  "label": { "ko": "다크 모드", "en": "Dark Mode", "ja": "ダークモード" },
  "value": false
}
FieldTypeDefaultDescription
labelstring | i18nDescription text shown left of the switch
valuebooleanfalseInitial checked state

type: "separator"

Thin horizontal divider line for grouping items. No id, no callback.

{ "type": "separator" }

type: "header"

Non-interactive section title label. No callback.

{
  "type": "header",
  "label": { "ko": "설정", "en": "Settings", "ja": "設定" }
}

title Field

button, select, toggle, and image items support an optional title field. When set, a small secondary-text label is rendered directly above the control. Accepts a plain string or an i18n object.

{
  "id": "dark",
  "type": "toggle",
  "title": { "ko": "화면", "en": "Appearance", "ja": "画面" },
  "label": { "ko": "다크 모드", "en": "Dark Mode", "ja": "ダークモード" },
  "value": false
}

Multilingual Labels

All item label fields accept either a plain string or an i18n object with ko, en, ja keys.

// Plain string — same label regardless of locale
{ id: "home", label: "Home", icon: "home.png" }

// i18n object — label switches with setLocale()
{ id: "home", label: { ko: "홈", en: "Home", ja: "ホーム" }, icon: "home.png" }

When an i18n object is used, the displayed label is selected by the current locale (see setLocale below). The fallback order is: requested locale → en → item id.


setLocale

Override the locale used for menu item label display. Call this when the user changes the language in your web app.

window.unveilyBridge.app.setLocale("ko");  // "ko" | "en" | "ja"
window.unveilyBridge.app.setLocale("");    // reset to device locale

Labels in open panels update immediately without reopening.

Example — language selector in Top Down Menu

window.unveilyBridge.topDownMenu.setConfig({
  items: [
    { id: "home", label: { ko: "홈", en: "Home", ja: "ホーム" }, icon: "home.png" },
    { id: "lang", type: "select", value: "en", options: [
        { value: "ko", label: { ko: "한국어", en: "Korean",   ja: "韓国語" } },
        { value: "en", label: { ko: "영어",   en: "English",  ja: "英語"   } },
        { value: "ja", label: { ko: "일본어", en: "Japanese", ja: "日本語" } }
    ]}
  ]
});

function onTopDownMenuAction(result) {
  if (result.id === "lang" && result.type === "select") {
    window.unveilyBridge.app.setLocale(result.value);
  }
}

On this page