IntegrationsShopify

Custom Storefront UI

Customize an existing PSYKHE AI Shopify integration with theme CSS and supported storefront hooks.

Use the approaches on this page when the PSYKHE AI Shopify app and theme blocks are already installed and configured. These examples change presentation and theme behaviour; they do not replace the integration's API configuration, search state, or analytics.

Add custom code to a draft or unpublished theme first. Keep the customization scoped to the theme container that holds the PSYKHE AI blocks.

Choose The Smallest Customization

Start with the least invasive option that meets the storefront requirement:

RequirementSupported approach
Change fonts, colours, spacing, radii, or grid gapsCSS custom properties
Style a specific element inside a PSYKHE AI component::part()
Replace a small piece of supplied contentA documented slot
Configure product cards beyond CSS alonerenderCard(product, ctx, meta)
Add theme-owned search or collection linkssuggestionsFetcher and renderQueryChip
Reinitialize custom JavaScript when Shopify reloads a section in the theme editorshopify:section:load

Prefer CSS custom properties and ::part() before replacing markup with JavaScript.

Theme CSS Custom Properties

CSS custom properties apply across the PSYKHE AI components inside their container. The following example aligns the integration with a neutral Shopify theme while keeping the rules scoped:

.psykhe {
  --psykhe-ui-font-family: var(--font-body-family, inherit);
  --psykhe-ui-text-color: var(--color-foreground, #121212);
  --psykhe-ui-surface-color: var(--color-background, #ffffff);
  --psykhe-ui-border-color: color-mix(
    in srgb,
    var(--color-foreground, #121212) 18%,
    transparent
  );
  --psykhe-ui-accent-color: var(--color-foreground, #121212);
  --psykhe-ui-radius-md: 0;
  --psykhe-ui-button-radius: 0;
  --psykhe-ui-card-aspect-ratio: 4 / 5;
  --psykhe-ui-grid-gap: 1.5rem;
}

Use literal fallback values because Shopify themes do not all expose the same CSS variables.

Styling Component Parts

PSYKHE AI components use Shadow DOM. Supported ::part() selectors let the theme style specific component elements without depending on internal class names or DOM structure.

/* Product cards rendered inside the product grid */
.psykhe psykhe-product-grid::part(vendor) {
  color: color-mix(
    in srgb,
    var(--color-foreground, #121212) 65%,
    transparent
  );
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.psykhe psykhe-product-grid::part(title) {
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.35;
}

.psykhe psykhe-product-grid::part(price) {
  font-size: 0.875rem;
}

.psykhe psykhe-product-grid::part(compare-price) {
  opacity: 0.6;
}

/* Filters and active refinements */
.psykhe psykhe-filter-panel::part(pill-trigger),
.psykhe psykhe-active-filters::part(chip) {
  border-radius: 999px;
}

/* Search */
.psykhe psykhe-search-input::part(input) {
  border-radius: 0;
  border-width: 0 0 1px;
}

.psykhe psykhe-search-input::part(suggestions) {
  border: 1px solid color-mix(
    in srgb,
    var(--color-foreground, #121212) 18%,
    transparent
  );
  box-shadow: none;
}

.psykhe psykhe-search-input::part(query-chip) {
  border-radius: 999px;
}

/* Mobile drawer */
.psykhe psykhe-drawer::part(panel) {
  width: min(92vw, 28rem);
}

.psykhe psykhe-drawer::part(header),
.psykhe psykhe-drawer::part(footer) {
  border-color: color-mix(
    in srgb,
    var(--color-foreground, #121212) 18%,
    transparent
  );
}

Commonly useful parts include:

ComponentParts
psykhe-product-gridgrid, item, plus product-card parts re-exported by its renderer
psykhe-product-cardcard, media, image, vendor, title, pricing, price, compare-price, badge
psykhe-filter-panelbase, pill-trigger, option-label, input
psykhe-active-filtersbase, chips, chip, chip-label, remove
psykhe-search-inputbase, form, input, clear, suggestions, query-chip, vendor, title, price
psykhe-drawerbackdrop, panel, header, heading, body, footer, close-button

Only target documented part names. Do not traverse a component's shadow root or depend on internal classes. Product cards inside psykhe-product-grid cross two shadow boundaries, so target the parts re-exported by the grid as shown above. Use psykhe-product-card::part(...) only when the card is a direct child of theme-owned markup.

Custom Product Cards

Use a render callback when the theme needs per-card configuration or slotted content that CSS cannot provide. A robust Shopify integration normally keeps psykhe-product-card for its images, pricing, navigation, accessibility, and tracking, then configures it from the callback.

The third callback argument contains the card's position. Pass that index to the product card and re-export any parts the theme needs to style:

async function installThemeProductCards(root = document) {
  await Promise.all([
    customElements.whenDefined("psykhe-product-grid"),
    customElements.whenDefined("psykhe-product-card"),
  ]);

  root.querySelectorAll("psykhe-product-grid").forEach((grid) => {
    if (grid.dataset.themeCardInstalled === "true") return;
    grid.dataset.themeCardInstalled = "true";

    grid.renderCard = (product, ctx, meta) => ctx.html`
      <psykhe-product-card
        class="theme-product-card"
        exportparts="card,link,media,image,vendor,title,pricing,price,compare-price,badge"
        .product=${product}
        .index=${meta.index}
        .showVendor=${false}
        .showCompareAtPrice=${true}
        .showSalePriceFirst=${true}
      ></psykhe-product-card>
    `;

    grid.requestUpdate?.();
  });
}

Keep the normalized product object intact rather than reconstructing it from platform-specific metadata. This lets the product card retain the normal SDK behaviour while the theme controls its presentation through properties, slots, and exported parts.

Quick links do not require a separate predictive-search theme component. The theme can wrap the suggestion fetcher already assigned to psykhe-search-input, then add store-owned collection URLs to the query-chip row. Production integrations can derive these links from fetched product metadata; this smaller example uses a merchant-approved list of store URLs.

async function installSearchQuickLinks(root = document) {
  await customElements.whenDefined("psykhe-search-input");

  root.querySelectorAll("psykhe-search-input").forEach((input) => {
    if (input.dataset.themeQuickLinksInstalled === "true") return;
    if (typeof input.suggestionsFetcher !== "function") return;

    input.dataset.themeQuickLinksInstalled = "true";
    const fetchSuggestions = input.suggestionsFetcher.bind(input);
    const quickLinks = [
      { value: "new-arrivals", label: "New arrivals", url: "/collections/new-arrivals" },
      { value: "dresses", label: "Dresses", url: "/collections/dresses" },
      { value: "accessories", label: "Accessories", url: "/collections/accessories" },
    ];

    input.suggestionsFetcher = async (query, options) => {
      const result = await fetchSuggestions(query, options);
      const products = Array.isArray(result) ? result : result.products;
      const existingQueries = Array.isArray(result) ? [] : result.queries ?? [];

      return {
        products,
        queries: existingQueries.length ? existingQueries : quickLinks,
      };
    };

    input.renderQueryChip = (suggestion, ctx) => {
      const label = suggestion.label ?? suggestion.value;

      if (!suggestion.url) {
        return ctx.html`
          <button
            part="query-chip"
            type="button"
            @mousedown=${(event) => event.preventDefault()}
            @click=${() => ctx.actions.selectSuggestion(suggestion)}
          >${label}</button>
        `;
      }

      return ctx.html`
        <a
          part="query-chip"
          href=${suggestion.url}
          @mousedown=${(event) => event.preventDefault()}
        >${label}</a>
      `;
    };

    input.requestUpdate?.();
  });
}

The collection handles above are examples. Use URLs that exist in the Shopify store, or render merchant-managed collection links into the theme before installing the callback. The PSYKHE AI hook controls presentation; Shopify remains the source of truth for collection URLs. Localize any labels displayed to shoppers through the theme's normal translation or merchant-setting flow.

Shopify Theme-Editor Lifecycle

Theme sections can be re-rendered without a full page load. Keep installation idempotent and run it again for newly loaded sections:

async function installPsykheThemeCustomizations(root = document) {
  await Promise.all([
    installThemeProductCards(root),
    installSearchQuickLinks(root),
  ]);
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", () => {
    installPsykheThemeCustomizations();
  }, { once: true });
} else {
  installPsykheThemeCustomizations();
}

document.addEventListener("shopify:section:load", (event) => {
  installPsykheThemeCustomizations(event.target);
});

If a customization adds its own long-lived event listeners, remove those listeners on shopify:section:unload.

Tracking Boundary

The examples above extend PSYKHE AI components and preserve their supplied actions. Do not publish duplicate list-view or product-click events from these callbacks.

Use Custom UI Tracking only when custom theme code owns the complete product grid and PSYKHE AI components are not managing the list experience.

Integration Guidance

  • Keep selectors scoped to the theme's PSYKHE AI container.
  • Wait for a custom element before assigning JavaScript properties to it.
  • Make installation idempotent so Shopify section reloads do not wrap callbacks repeatedly.
  • Preserve supplied ctx.actions handlers when replacing interactive markup.
  • Use real Shopify product and collection URLs rather than reconstructing routes from labels.
  • Test keyboard navigation, mobile drawers, filtering, sorting, pagination, and browser-back behaviour in a draft theme.
  • Avoid internal package paths, shadow-root traversal, and copied integration source.

On this page