I am developing a Shopify wishlist feature using a Theme App Extension. The wishlist works correctly on some themes, but I am facing alignment/functionality issues on other themes because different themes use different HTML structures and CSS classes.
I want to make the wishlist extension as theme-independent/universal as possible, without relying on theme-specific classes.
What would be the recommended Shopify approach for building a wishlist extension that works consistently across different Online Store 2.0 themes?
Hi @Anushka_78 Welcome To Shopify Community So The key principle here is to rely only on Theme App Extension’s own scaffolding (app blocks and app embed blocks) rather than assuming or targeting a specific theme’s existing HTML/CSS classes, since app blocks render inside their own isolated container that the merchant places via the Theme Editor, this container’s structure is controlled by you, not the theme, so it stays consistent regardless of which theme it’s added to. Style everything using your own scoped CSS (prefixed classes unique to your app, like data-wishlist-app attributes or a unique namespace) inside your block’s Liquid/CSS, and avoid writing any CSS that assumes a parent theme class exists around it, since that’s exactly what breaks when themes structure their DOM differently. For the actual wishlist button/icon placement inside product cards or product pages (which does require sitting inside the theme’s existing markup, not just a standalone app block section), you’re somewhat theme-dependent no matter what, since injecting into product card markup requires either the merchant manually adding an app block there via the Theme Editor (works cleanly on OS 2.0 sections/blocks-based themes) or using a theme.liquid/snippet injection approach for older or heavily customized sections, which is inherently more fragile across themes. If full universality matters more than deep native integration, consider a floating/fixed wishlist button and a separate wishlist page as app blocks, that sidesteps needing to hook into each theme’s product card structure entirely, at the cost of not looking as natively “built-in” as an inline heart icon would. Hope this helps solve your problem, and if it does, don’t forget to like and mark it as the solution. Thank you!
I’d avoid trying to make the extension detect every theme’s product card classes. That gets messy pretty quickly since every theme structures those elements differently. I’d give the wishlist button its own app block/markup and use your own data attributes for the product or variant ID, then keep the JS and CSS scoped to that markup.
For product pages, the app block approach works pretty well since the merchant can place it wherever they want in the theme editor. Collection/product-card placement is the harder part because there isn’t really one universal selector you can rely on across OS 2.0 themes. If you need automatic placement there, you’ll probably still need some theme-specific fallback logic.
Also make sure the JS can initialize again when Shopify re-renders a section. Otherwise it’ll work on initial load but randomly stop working after a section gets updated.
I’d avoid making the wishlist dependent on theme specific HTML structures or CSS classes. Different OS 2.0 themes can have completely different markup, so selectors like .product-card can easily break.
For a Theme App Extension, I’d use App Blocks or an App Embed Block depending on where the wishlist UI needs to appear. This gives the merchant a more reliable way to place the wishlist functionality through the theme editor.
For identifying products, use stable Shopify identifiers such as the product ID or variant ID rather than relying on CSS classes. I’d also keep the extension’s CSS properly scoped so the theme’s styles don’t interfere with the wishlist UI
If the button needs to be added automatically to different product cards, some theme specific differences will still exist. In that case, I’d keep the DOM assumptions to a minimum and provide a fallback instead of trying to support every theme with custom selectors.
Overall, App Blocks/Embed Blocks + Shopify product/variant IDs + scoped CSS + minimal DOM dependency would be the approach I’d recommend for making the wishlist as theme independent as possible.
I’d stop trying to make the wishlist read the theme’s product card markup and instead give it one stable target of your own.
In a theme app extension, you can render the wishlist button as an app block or app embed, then attach it to a predictable wrapper you control, with your own class names and data attributes so the theme’s CSS doesn’t decide where it lands.
For the product page, I’d use the product form and product JSON as the source of truth. For collection grids, keep the wishlist action inside the block output itself, because I’ve seen the same app behave fine on one theme and drift badly on another just from spacing, flex rules, or a card component changing.
If you need it to survive across themes, the cleanest pattern is usually one block for product pages and one embed for global behavior, then a tiny bit of CSS scoped only to your app namespace. That keeps the install portable and avoids chasing theme classes every time a merchant switches themes.
The four answers above are all right about not reading theme classes. The piece nobody has mentioned is the other direction: stopping the theme’s CSS from reaching you.
Scoped class names and namespaced selectors protect the theme from your styles. They do not protect your styles from the theme. Themes ship broad descendant selectors, and eventually one of them lands on your markup and your button is 4px tall on exactly one theme.
Render your UI into a Shadow DOM root instead. Inherited properties still cross the boundary, so you inherit the font and look native, but the theme’s rules cannot reach inside. That removes the whole class of “works on Dawn, broken on Prestige” bug rather than playing whack-a-mole per theme.
Two things that come with it, so you are not surprised: your CSS has to be injected inside the shadow root rather than linked in the head, and anything that must escape the container, like a modal or a dropdown, needs portalling to a root outside it or it will be clipped by the theme’s own overflow and stacking contexts. Container queries rather than viewport media queries also help, since your block can sit in a narrow sidebar on one theme and full width on another.
For anchoring, mount into your app block wherever the merchant places it and take product and variant IDs from the block settings or the product JSON, rather than locating a product card in the DOM.
I build a registry and wishlist app on this pattern and it is the thing that made cross-theme support stop being a per-theme chore.