Custom Styling Shopify Embedded Apps: Bypassing s-link & s-clickable Defaults
Hey there, fellow Shopify developers and store owners! We recently had a fascinating discussion in the community that really hit home for anyone building embedded apps for the Shopify admin. It revolved around a common frustration: trying to apply custom styling to Shopify's new web components, specifically and , and finding your styles just wouldn't stick. Let's dive into what we learned and how you can navigate this challenge.
The Custom Styling Conundrum with Shopify Web Components
Our community member, dvendy, kicked off the thread with a familiar scenario. They were building an embedded Shopify admin app using a React Router/Hydrogen-style setup and wanted to use the new and components for internal navigation. Sounds straightforward, right? The navigation itself worked perfectly, but dvendy was trying to fully custom-style these components with Tailwind CSS.
The problem? These components applied their own built-in interaction styles — think hover, pressed, and focus states with a subtle gray overlay and default link styling. And no matter what dvendy tried — text-decoration: none, background: transparent !important, targeting the components directly, even ::part() selectors — those default styles just wouldn't budge. The default hover/pressed gray state kept overriding their custom button and card designs. dvendy's goal was clear: use these components only for their navigation behavior, making them visually transparent containers for their custom Tailwind UI.
Why Your Custom Styles Aren't Working (The Shadow DOM Secret)
This is where the community, particularly anmolkumar, provided some crucial insights. The core reason dvendy's overrides weren't working lies in how these Shopify web components are built. They're constructed using Shadow DOM. Think of Shadow DOM as an encapsulated, self-contained part of the DOM within a component. It's like a mini-website inside your component, and its styles are intentionally isolated from the rest of your page's CSS.
Here's a quick breakdown of why your normal CSS and Tailwind utilities struggle:
- Shadow DOM Blocks Normal CSS/Tailwind: Because the components render inside Shadow DOM, your global CSS or even targeted styles from outside the Shadow DOM boundary can't easily penetrate and override their internal styling. It's by design to ensure component consistency.
::part()Limitations: While::part()selectors *can* expose certain internal parts of a Shadow DOM component for styling, they only work if the component's author explicitly exposes those parts. In the case ofand, their interaction states aren't fully theme-replaceable via this method.- Internal Interaction States: Shopify's Polaris web components handle their hover, pressed, and focus states internally. They expect to be styled via Polaris design tokens, not entirely replaced with custom CSS frameworks like Tailwind UI.
Ultimately, the message is clear: Shopify's intent is that if you use Polaris components, you're embracing Polaris styling. If you want a full custom UI, you'll need to use your own components and manage navigation independently.
The Recommended Approach: Achieving Full Visual Control
So, what's a developer to do when you want that pixel-perfect custom look without fighting Shopify's built-in styles? Based on the discussion, here's the recommended approach:
1. For Internal App Routes (e.g., /page2 within your embedded app)
Do NOT use a standard HTML with target="_top" for internal navigation within your app. While it might seem like a quick fix, dvendy found that it can lead to losing your authentication session, which is a major headache!
Instead, embrace your routing library. If you're using React Router (as dvendy was), leverage its built-in components and programmatic navigation:
- Use React Router's
Component: This is the cleanest way to handle internal navigation.
Go to Page 2
navigate() function.navigate("/page2");
2. For Redirects to Shopify Admin (e.g., shopify://admin/products)
This is the specific scenario where target="_top" is appropriate. When you need to redirect the entire browser window to a Shopify Admin page or an external URL, use a standard tag with target="_top":
Products page
This tells the browser to open the link in the top-most browsing context, effectively breaking out of your embedded app frame to the main Shopify admin.
3. Achieving 100% Visual Control with Custom Components
If your goal is truly 100% visual control, and you want to use a framework like Tailwind for all your styling, then the advice is straightforward: don't use or for styling at all.
- Build Your Own Styled Components: Instead of trying to strip the styles from Polaris components, create your own custom components (e.g., a or
) and apply your Tailwind classes directly.- Trigger Navigation Programmatically: Attach an
onClickhandler to your custom component and use React Router'snavigate()function (as shown above) to handle the routing. This way, your component looks exactly how you want it, and the navigation happens behind the scenes.This approach means you're not fighting against Shadow DOM encapsulation or Shopify's intended styling. You're simply using the right tools for the job: React Router for internal app navigation and your own custom components for bespoke UI.
So, if you've been pulling your hair out trying to style
orcomponents with your custom CSS, remember this key takeaway from the community: use Polaris components when you want Polaris styling. But when you need full, uncompromised control over your UI's appearance, build your own components and integrate your navigation logic using tools like React Router. It's about making informed choices to build robust and visually consistent embedded apps. - Trigger Navigation Programmatically: Attach an