VIBE on custom themes
This reference covers custom VIBE integrations on Online Store 2.0 themes.
It documents:
- The
vibe.widget_configshop metafield that drives the storefront widget. - The runtime global (
window.__vibe) the widget exposes on the page. - The data attributes the theme app extension blocks render.
- The locale keys the widget reads for translatable UI text.
Audience: Theme developers and agencies. Merchants should configure VIBE in the app and use Enable VIBE on your theme.
Scope: This page documents the shipped widget and theme extension. Direct metafield edits are unsupported and can be overwritten by the app.
How the pieces fit together
Section titled “How the pieces fit together”
- The merchant configures VIBE in the app admin. The app writes the settings to a single shop metafield: namespace
vibe, keywidget_config(type JSON). - The theme app extension renders a hidden mount element on the storefront. It serializes the metafield value into a
data-widget-configattribute and adds a small set of environment data attributes (shop domain, proxy URL, locale, country, asset URLs). vibe-loader.jsboots on page load, parsesdata-widget-configand the locale data, normalizes everything into a frozenconfigobject, and publishes runtime state onwindow.__vibe.- The loader applies styling (CSS variables and classes on
<html>) and then dynamically imports the active mode bundle (overlay/fallback or native).
Two things follow from this design:
- The blocks themselves have no theme-editor settings. Both blocks declare
"settings": []. All runtime behavior comes from the metafield. You cannot configure VIBE from the theme editor’s block settings panel; you configure it in the app. - The widget runs in the light DOM (no shadow DOM). Styling is applied via CSS custom properties and classes on
document.documentElement(<html>).
The two theme app extension blocks
Section titled “The two theme app extension blocks”VIBE ships two blocks in the vibe-search theme app extension.
VIBE Search: app embed
Section titled “VIBE Search: app embed”- Source:
extensions/vibe-search/blocks/vibe-embed.liquid - Schema
target:body(an app embed, enabled per theme). - Renders an element with
id="vibe-search-root"anddata-vibe-root. - This is the block merchants enable to turn VIBE on across the storefront (header search, predictive/quick search).
Search Page: section block
Section titled “Search Page: section block”- Source:
extensions/vibe-search/blocks/vibe-search-page.liquid - Schema
target:section,enabled_on.templates:["search"](only available on the search template). - Renders an element with
data-vibe-rootanddata-vibe-surface="search-page", plus a separate mount element withdata-vibe-search-page-mountanddata-vibe-search-action="/search". - Used to render the VIBE search results experience directly on the storefront search page.
Both blocks load the same assets at the end of their markup:
<script src="{{ 'vibe-loader.js' | asset_url }}" type="module"></script><link rel="stylesheet" href="{{ 'vibe-widget.css' | asset_url }}" media="all">Root element selection
Section titled “Root element selection”The loader (widget-src/loader.js) selects which mount element (“root”) to use:
- It collects all elements matching
[data-vibe-root]and#vibe-search-root(de-duplicated). - On the main search page, if any root has
data-vibe-surface="search-page", that root is used. - Otherwise it uses
#vibe-search-root, falling back to the first[data-vibe-root]element found.
If no root is found, the loader throws [VIBE] #vibe-search-root not found and does nothing. If a root is found but has no data-proxy-url, the loader throws [VIBE] No proxyUrl configured.
This means a custom theme integration must, at minimum, render an element with data-vibe-root (or id="vibe-search-root") carrying a valid data-proxy-url. The simplest and supported way to get this markup is to enable the shipped app embed.
Data attributes rendered by the blocks
Section titled “Data attributes rendered by the blocks”The blocks render these attributes on the root element. The loader reads them as the environment/bootstrap inputs.
| Attribute | Source (Liquid) | Purpose |
| --------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| id="vibe-search-root" | static (app embed only) | Primary root selector. |
| data-vibe-root | static | Root selector (both blocks). |
| data-vibe-surface="search-page" | static (search-page block only) | Marks the search-page root so it wins selection on the search template. |
| data-proxy-url | {{ shop.url }}/apps/search | App Proxy endpoint the widget calls for search, controls, and tracking. Required. |
| data-shop | {{ shop.permanent_domain }} | The shop’s permanent .myshopify.com domain. |
| data-locale | {{ localization.language.iso_code | default: request.locale.iso_code }} | Active storefront language ISO code; used to pick a locale translation override. |
| data-country | {{ localization.country.iso_code }} | Active storefront country ISO code. |
| data-native-src | {{ 'vibe-native.js' | asset_url }} | CDN URL of the native-mode bundle. |
| data-fallback-src | {{ 'vibe-fallback.js' | asset_url }} | CDN URL of the overlay/fallback-mode bundle. |
| data-fallback-css | {{ 'vibe-fallback.css' | asset_url }} | CDN URL of the overlay/fallback-mode stylesheet. The loader injects it as a <link> when it loads vibe-fallback.js; only the slim vibe-widget.css base is render-blocking. |
| data-i18n | per-key {{ "vibe_search.\*" | t | json }} | JSON object of translated UI strings from the theme’s locale files (see Locale keys). |
| data-widget-config | {{ widget_config | json | escape }} | The serialized vibe.widget_config metafield value (JSON). Defaults to {} when the metafield is empty. |
The search-page block additionally renders a second mount element:
| Attribute | Source (Liquid) | Purpose |
|---|---|---|
data-vibe-search-page-mount | static | Mount point for the search-page results UI. |
data-vibe-search-action="/search" | static | The storefront search form action the search-page experience targets. |
The root element is rendered hidden (style="display:none" and aria-hidden="true"); it is a configuration carrier, not visible UI.
Attributes the loader sets at runtime
Section titled “Attributes the loader sets at runtime”The loader sets a small number of attributes/classes on document.documentElement (<html>):
vibe-cards--simple/vibe-cards--detailed: card style class.vibe-cols--3/vibe-cols--4: results grid columns class.vibe-fonts--theme: present when font mode istheme(inherit the storefront font); absent when font mode isvibe.- CSS variables
--vibe-radius-card,--vibe-radius-sm,--vibe-spacing: numeric pixel values for corner radius and component spacing. --vibe-<name>color variables (for example--vibe-accent): set only whencolorSchemeModeiscustom(see Color scheme).data-vibe-overlay="true"on<html>: set when the widget runs in overlay mode and the overlay guard is installed.
The vibe.widget_config metafield
Section titled “The vibe.widget_config metafield”The metafield is a single JSON object. Every field is optional; the loader applies a defined default (or derives a value) for each one. Where a field is not present in the metafield, the loader also accepts a matching data-* attribute on the root for some keys (noted below), but the metafield is the primary, app-managed source.
Field names below are exactly the JSON keys the loader reads from the parsed widgetConfig object.
Appearance
Section titled “Appearance”| Field | Type | Default | Notes |
|---|---|---|---|
cardStyle | string | detailed | Only simple or detailed; any other value falls back to detailed. Also accepts data-card-style. |
cornerRadius | number (px) | 8 | Clamped from 0 to 32, rounded. Sets --vibe-radius-card (and --vibe-radius-sm at ~0.75x). Also accepts data-corner-radius. |
componentSpacing | number (px) | 16 | Clamped from 0 to 48, rounded. Sets --vibe-spacing. Also accepts data-component-spacing. |
gridColumns | number | 4 | Only 3 or 4; any other value falls back to 4. Also accepts data-grid-columns. |
fontMode | string | theme | theme (inherit storefront font) or vibe (use the widget’s own font). Any value other than vibe is treated as theme. Also accepts data-font-mode. |
colorSchemeMode | string | default | When custom, the widget applies customColors. See Color scheme. Also accepts data-color-scheme-mode. |
customColors | object | none | Map of color-token name to CSS color string. Applied only when colorSchemeMode is custom. See the token list below. |
Color scheme
Section titled “Color scheme”When colorSchemeMode is custom, the loader reads customColors (an object) and sets the corresponding --vibe-<name> CSS variables on <html>. Only string values are applied. Recognized token names (from widget-src/lib/color-scheme.js):
| Token | Default |
|---|---|
bg-1 | #FFFFFF |
bg-2 | #F5F6F8 |
primary-text | #3A3636 |
secondary-text | #FFFFFF |
accent | #590FB8 |
accent-secondary | #D8C4F9 |
primary-gray | #767C85 |
secondary-gray | #AFBCCD |
tertiary | #D8DDE2 |
When colorSchemeMode is not custom, these defaults (defined in the widget stylesheet) apply and customColors is ignored.
Behavior and hints
Section titled “Behavior and hints”| Field | Type | Default | Notes |
|---|---|---|---|
overlayMode | string | overlay | overlay forces the fullscreen overlay; native opts out (inject into the theme’s own search UI). Any other value defaults to overlay. |
placeholder | string | search_placeholder locale value, else Search products, collections | Search input placeholder. Also accepts data-placeholder. |
resultsCount | number | 12 | Main results page size. Clamped from 1 to 48 (packages/shared/src/quick-search-limits.ts). Also accepts data-results-count. |
quickSearchAutocompleteEnabled | boolean | true | Set false to disable autocomplete suggestions on the quick (predictive) search surface; any value other than the boolean false is treated as enabled. |
searchPageAutocompleteEnabled | boolean | true | Set false to disable autocomplete suggestions on the full search page; any value other than the boolean false is treated as enabled. |
showSearchHint | boolean | true | Show the search hint text. Also accepts data-show-search-hint. |
searchHintText | string | Try natural language like "cozy autumn jacket" | Hint text shown with search. Also accepts data-search-hint-text. |
showExploreHint | boolean | true | Show the explore (sliders) hint. Also accepts data-show-explore-hint. |
exploreHintText | string | Adjust fine-details with sliders | Explore hint text. Also accepts data-explore-hint-text. |
gtmEventsEnabled | boolean | false | When true, the widget pushes Google Tag Manager dataLayer events. See GTM events. |
abTestActive | boolean | false | When true, the widget assigns the session to an A/B group (see A/B testing). |
Quick search (predictive) limits
Section titled “Quick search (predictive) limits”These cap how many of each result type appear in quick (predictive) search. They are sanitized to integers >= 1 and clamped to a maximum (packages/shared/src/quick-search-limits.ts).
| Field | Type | Default | Max |
|---|---|---|---|
quickSearchProductLimit | number | 4 | 8 |
quickSearchCollectionLimit | number | 3 | 8 |
quickSearchSuggestionLimit | number | 3 | 3 |
Search-page integration
Section titled “Search-page integration”These control how VIBE renders on the storefront search template.
| Field | Type | Default | Notes |
|---|---|---|---|
searchPageMode | string | disabled | One of app-block, embed-replace, disabled. Any other value falls back to disabled. |
searchPageTemplateKind | string | unknown | Detected template type: json, liquid, or unknown. |
searchPageRecommendedIntegration | string | unknown | Recommended integration: app-block, app-embed, or unknown. |
searchPageAppBlockInstalled | boolean | false | Whether the Search Page app block is detected as installed. |
searchPageReplaceSelector | string | "" | Selector of the theme element to replace in embed-replace mode. |
useNativeSearchPageCards | boolean | false | Render results using the theme’s native product card. Effective only when true and searchPageProductCardSelector is set. |
searchPageProductCardSelector | string | "" | Selector for the theme’s search-page product card. |
searchPagePriceSelector | string | "" | Selector for the price element within the native card. |
searchPageCompareAtSelector | string | "" | Selector for the compare-at price within the native card. |
Search input selectors
Section titled “Search input selectors”VIBE locates the storefront’s search inputs and containers using selectors. Most have desktop/mobile variants that fall back to the base selector when unset, and several have a hard-coded default targeting the standard Shopify search form.
The base selectors:
| Field | Default |
|---|---|
quickSearchSelector | "" |
quickSearchInputSelector | form[action="/search"] input[name="q"] |
quickSearchPopularQueriesSelector | "" |
quickSearchSuggestionsSelector | "" |
mainSearchSelector | "" |
mainSearchInputSelector | form[action="/search"] input[name="q"] |
mainSearchSuggestionsSelector | "" |
mainSearchProductListSelector | "" |
For each of the quick- and main-search groups, the loader also reads desktop and mobile variants and falls back to the base value (and, for input selectors, to the standard form default) when a variant is unset:
- Quick:
quickSearchDesktopSelector,quickSearchDesktopInputSelector,quickSearchDesktopPopularQueriesSelector,quickSearchDesktopSuggestionsSelector, and the matchingquickSearch Mobile*keys. - Main:
mainSearchDesktopSelector,mainSearchDesktopInputSelector,mainSearchDesktopSuggestionsSelector,mainSearchDesktopProductListSelector, and the matchingmainSearchMobile*keys.
Most of these are detected automatically by VIBE’s theme analyzer and written into the metafield by the app. Set them manually only when integrating on a theme whose markup the analyzer cannot detect.
Advanced native-integration fields (analyzer-detected)
Section titled “Advanced native-integration fields (analyzer-detected)”The metafield can also carry a set of advanced fields used for deep “native” integration, where VIBE injects its results into the theme’s own predictive-search markup. These are normally populated automatically by VIBE’s theme analyzer and are not intended to be hand-authored. They are listed here for completeness; the loader reads each one and defaults it to an empty string, null, or false when absent.
| Field | Type | Default |
| --------------------------------------- | ------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| useNativeQuickCards | boolean | false |
| quickSearchProductCardSelector | string | "" |
| quickSearchPriceSelector | string | "" |
| quickSearchCompareAtSelector | string | "" |
| quickSearchCardSnippetName | string | "" |
| quickSearchCardRenderTagForProduct | string | "" |
| quickSearchCardStrategy | (any) | null |
| quickSearchOpenTarget | string | "" |
| quickSearchOpenAttributes | (any) | null |
| quickSearchOpenClasses | (any) | null |
| quickSearchResultsContainer | string | "" |
| quickSearchResultsGroups | (any) | null |
| quickSearchResultsCloneTemplate | (any) | null |
| quickSearchMobileResultsContainer | string | "" |
| quickSearchMobileResultsGroups | (any) | null |
| quickSearchMobileResultsCloneTemplate | (any) | null |
| quickSearchFetchMode | string | "" |
| quickSearchSectionId | string | "" |
| quickSearchGridSelector | string | "" |
| quickSearchCollectionPlacement | string | "" (only suggestions-list or after-products accepted) |
| translations | object | none | Per-locale translation overrides; see Per-locale overrides. |
Do not hand-edit the analyzer fields. They describe the exact markup of a specific theme and are regenerated whenever the app re-analyzes the theme. Manual values may be overwritten and can break native integration if they no longer match the theme.
The window.__vibe runtime global
Section titled “The window.__vibe runtime global”After bootstrap, the loader publishes a frozen object on window.__vibe. This is the supported read-only surface for integrations that need to observe VIBE’s runtime state.
window.__vibe = { config, // the normalized, frozen config object (see below) loaded: false, // set to true once the active mode bundle has initialized version: "2.0.0", // widget runtime version sessionId, // cookieless anonymous session id (or null) abGroup, // 'vibe' or 'control' abTestActive, // boolean: whether an A/B test is running controlsPrefetchPromise, // Promise of prefetched semantic controls, or null};Notes:
window.__vibeis replaced (not mutated) at key points.loadedbecomestrueafter the mode bundle’sinit()runs, andsessionIdis re-synced if the session id changes. Read it freshly rather than caching a reference.versionis the widget runtime version string as shipped inwidget-src/loader.js. It is diagnostic information, not a stable public API. Do not branch storefront behavior on a specific value.controlsPrefetchPromiseis non-null only when controls are prefetched (overlay mode, the search-page surface, or the main search page with search-page mode enabled) and the session is not in the A/B control group.
The config object
Section titled “The config object”window.__vibe.config is the normalized configuration the widget runs on. It is derived from the metafield, the data attributes, and built-in defaults, then frozen. Its keys mirror the metafield fields documented above (for example proxyUrl, shop, placeholder, resultsCount, cardStyle, cornerRadius, componentSpacing, gridColumns, fontMode, the quick-search and main-search selectors, the search-page fields, and an i18n object of resolved translations). A few keys are normalized to a different name or shape than the raw metafield:
config key | Derived from | Notes |
|---|---|---|
proxyUrl | data-proxy-url | App Proxy base URL. |
shop | data-shop | Permanent shop domain. |
useOverlay | overlayMode | true for overlay, false for native. |
gtmEvents | gtmEventsEnabled | Boolean. |
quickSearchAutocompleteEnabled / searchPageAutocompleteEnabled | same metafield keys | Per-surface autocomplete toggles; true unless explicitly false. |
quickSearchProductLimit / quickSearchCollectionLimit / quickSearchSuggestionLimit | quick-search limits | Sanitized + clamped integers. |
i18n | data-i18n + translations override | Resolved translation map (see Locale keys). |
Treat config as read-only. Changing VIBE’s behavior must be done in the app (which updates the metafield); mutating config at runtime is unsupported and, because the object is frozen, has no effect.
Locale keys
Section titled “Locale keys”VIBE’s translatable UI text comes from the theme app extension’s locale files. The default file is extensions/vibe-search/locales/en.default.json, under the top-level vibe_search namespace. The blocks emit each key into the root’s data-i18n attribute using Shopify’s {{ "vibe_search.<key>" | t | json }} translation filter, so the active storefront language’s values are used automatically.
The available keys (from en.default.json):
| Key | English default |
|---|---|
products | Products |
collections | Collections |
search | Search |
explore | Explore |
close | Close |
clear_search | Clear search |
search_placeholder | Search products, collections |
deselect | Deselect |
in_collections | In Collections |
in_articles | In Articles |
explore_hint_sliders | Adjust fine-details with sliders |
reset | Reset |
all_collections | All |
updating_results | Updating results… |
concept_required | Add a concept before adjusting the value. |
load_more_results | Load more results |
sort_by | Sort by |
sort_relevance | Relevance |
sort_price_asc | Price: low to high |
sort_price_desc | Price: high to low |
sort_newest | Newest |
filters | Filters |
min | Min |
max | Max |
in_stock | In stock |
out_of_stock | Out of stock |
no_results | No results |
no_results_title | Your search did not return any results. |
no_results_hint | Check your spelling or try again with a less specific keyword. |
explore_no_results_hint | Try adjusting the Explore controls or clearing filters. |
search_again | Search again |
Supported languages.
en.default.jsonis the bundled widget locale. To translate the storefront widget UI, add standard Shopify extension locale files (for examplefr.json) with the samevibe_searchkeys; the blocks’{{ ... | t }}filters resolve them per the storefront’s active language.
Per-locale overrides
Section titled “Per-locale overrides”The loader supports an additional override layer via the translations object in the metafield. If present, it is keyed by locale code. For the active locale (from data-locale, or window.Shopify.locale, default en), the loader looks up an exact match, then the base language (the part before -), and merges that object’s strings over the data-i18n values. Keys match the locale keys above. This lets the app override specific strings per language without editing the theme’s locale files.
Sessions, tracking, and privacy notes
Section titled “Sessions, tracking, and privacy notes”VIBE uses a cookieless, anonymous session model (widget-src/core/session.js):
- A random session id is stored in
sessionStorageundervibe_sid, with a timestamp undervibe_sid_ts. - The session expires after 30 minutes of inactivity; a new id is generated on the next request.
- No cookies are set, and no personally identifiable information is collected by the session layer.
- The session id is also reflected on
window.__vibe.sessionId.
The loader also installs cart-add attribution tracking: it listens for common theme cart events (cart:line_item_added, cart:line-item-added, ajaxProduct:added, product:added) and patches window.fetch to detect /cart/add (and /cart/add.js) requests, so an add-to-cart can be attributed to a prior VIBE result click recorded in sessionStorage (vibe_last_click). All tracking calls go to the App Proxy (data-proxy-url).
GTM events
Section titled “GTM events”When gtmEventsEnabled is true in the metafield, the widget pushes events to window.dataLayer (creating it if needed) via pushGTM. If GTM events are disabled, no dataLayer writes occur. GTM is only active when the merchant enables it; VIBE does not load GTM itself.
A/B testing
Section titled “A/B testing”When abTestActive is true and a session id exists, the loader derives the A/B group from the last hex digit of the session id. Even values use vibe; odd values use control. For the control group, the widget does not render. It installs attribution tracking on the theme’s native search input. The active group is exposed as window.__vibe.abGroup.
Integration checklist for a custom theme
Section titled “Integration checklist for a custom theme”- Enable the app embed. Add the
VIBE Searchapp embed to the theme (the in-app theme editor deep link is the supported path). This renders the#vibe-search-rootelement with the required environment attributes. - Confirm the proxy URL resolves. The root must carry a
data-proxy-url(the blocks set it to{{ shop.url }}/apps/search). Without it, the loader throws and does nothing. - Check the search input selectors. If the theme’s search markup is non-standard, VIBE’s analyzer may not detect it. The default input selector is
form[action="/search"] input[name="q"]; if your theme differs, the app’s analysis should capture the right selectors into the metafield. Avoid hand-editing the analyzer-detected fields. - Decide overlay vs. native.
overlayModecontrols whether VIBE opens its own fullscreen overlay (overlay) or injects into the theme’s search UI (native). Set this in the app. - (Optional) Enable the Search Page block on the
searchtemplate if you want VIBE to power the full search results page. This adds the search-page root (data-vibe-surface="search-page"), which wins root selection on that template. - Verify locale coverage. Ensure the storefront language has a matching extension locale file, or rely on the English default. Use the metafield
translationsoverride only for app-managed per-locale string changes. - Test. Append
?vibe_debugto a storefront URL to enable loader debug logging during integration.
Reference: source files
Section titled “Reference: source files”| Concern | File |
|---|---|
Loader / config normalization / window.__vibe | widget-src/loader.js |
| Cookieless session + GTM helper | widget-src/core/session.js |
| Color tokens and custom color application | widget-src/lib/color-scheme.js |
| Quick-search and results limit defaults/maxes | packages/shared/src/quick-search-limits.ts |
| App embed block | extensions/vibe-search/blocks/vibe-embed.liquid |
| Search page block | extensions/vibe-search/blocks/vibe-search-page.liquid |
| Default locale strings | extensions/vibe-search/locales/en.default.json |