Skip to content

Writing

Running a styles and JavaScript test: power, and the guardrails

Inject scoped CSS and JS per group on the theme you already have. Nothing to set up in Shopify, and the one test type that can genuinely break a storefront.

12 September 2026 · Updated 5 October 2026 · 7 min read

A styles test applies a CSS blob, and optionally a JavaScript blob, per group, on the theme you already have. Nothing to build in Shopify, no second theme, no template to duplicate. It is the most capable of the content tests and the only one that can take a storefront down.

The figures below are live. Flip the groups, drag the sliders, and pick the wrong option on purpose — that is what they are for.

Setting one up

Each group gets two editors on the Modifications tab: one for CSS and one for JavaScript. Leave one group empty. That group is your store as it is, and is normally the control. Give each other group the CSS that defines it.

It works on a headless store too. The loader script injects the CSS and runs the JavaScript there exactly as it does on a theme.

Fig. 01One stylesheet per group
Group

B · Bigger button: its stylesheet

Applied before the page is shown.

The selector matches, so every shopper in this group sees the full-width button from the first paint.

↳ A selector that misses fails silently.

Groups B and C are the kind of question this test type answers well. Group D is the one to watch for. Its CSS is perfectly valid, and it does nothing, because the class it names is not on the page. A selector that stops matching does not error. The group then reports as “no different” when it was never applied at all.

Finding a selector that will hold

The styles editor has no element picker. You write the selector yourself, so find it in your browser’s devtools: right-click the element, Inspect, and read its attributes.

  • Prefer an id or a data- attribute. They are named on purpose and tend to survive a theme update.
  • Be wary of classes. Themes rewrite class names on update, which is exactly how group D above came about.
  • Never anchor to Shopify’s generated section ids. Ids like shopify-section-template--17234567890123__hero are regenerated, so a rule built on one works until it doesn’t.
  • Borrow a content test’s picker if it helps. The element picker belongs to content tests, and builds its selectors by these same rules. Picking the element in a draft content test gives you one you can copy.

Prefer CSS. Genuinely.

Most questions worth asking — is this button big enough, does this section need more room, is the badge too loud — are CSS questions. CSS cannot throw, cannot loop, and cannot leave a page half-built.

It also lands in time. The loader adds a group’s CSS to the document head before the page is shown, and a CSS rule applies to an element whenever that element is parsed. A script only touches what exists at the moment it runs.

Fig. 02When the change lands
How the group changes the button
700 ms
What the shopper sees

Page hidden from 0 to 200 ms, Original showing from 200 to 700 ms, Variant showing from 700 to 1500 ms; page parsed at 700 ms

Page hiddenOriginal showingVariant showing
Original visible for500 ms

Shoppers see the original for 500 ms first.

The overlay has already come off when the script gets its element. That flash is part of what this group is measuring.

JS, on DOMContentLoaded: the group's code

↳ Only CSS never flashes.

Read it back. CSS is green at every slider position. The script that runs at once works only when the page happened to be parsed already, and otherwise changes nothing. The script that waits for DOMContentLoaded shows shoppers the original first, and misses entirely when the page finished before it ran. The careful version never misses, and still flashes on a slow page.

If the question can be asked in CSS, ask it in CSS.

If you do write JavaScript

The editor says it plainly: “A thrown error is caught, so it can’t block the page.” Your code runs wrapped, and an error is logged to the console rather than stopping the page being revealed. That is a floor rather than a safety net. It stops your mistake blanking the store, not from doing whatever else it did first.

  • Make it idempotent. It may run alongside other apps re-rendering the same DOM. Write it so running twice is harmless.
  • Handle both timings. Check document.readyState, as in the last option above, rather than assuming the page is or isn’t ready.
  • Do not touch the cart or the checkout. Changing what a shopper is charged from a browser is not something this test type can do safely. That is what offer and price tests are for, and they do it server-side for exactly this reason.
  • Check the console on your own storefront. Open the preview link, press F12, and look for a red [zinx-signal] group script failed before you start the test rather than after.

What it is good at

Layout and emphasis questions where the content is already right. Button size and colour. Spacing around a section. Whether a sticky add-to-cart bar earns its place. Reordering blocks that are already on the page.

What it is poor at is anything a theme should own permanently. If the honest answer is “this page should be laid out differently”, a template test compares two real templates and leaves you with something you can publish, rather than a stylesheet served by us forever.

Before you start

  • Open a preview link per group. Save first. Preview reads what is saved, not what is on screen.
  • Check it on a phone. Your theme’s DOM may differ there, and a selector that matches on desktop may not.
  • Run Check setup on the Preview tab. It fails a group whose JavaScript will not compile, naming the line, and a stylesheet with an unclosed or extra brace — the one CSS mistake that silently drops every rule after it.
  • Check the URL rules on Targeting. An empty list means every page of your store, which for a stylesheet is usually right and occasionally very wrong.

When it wins

Move the winning CSS into your theme and end the test. Serving it from us permanently means every shopper waits on our script for a change that belongs in your stylesheet — and a test left running forever is a dependency you did not mean to take.

Read next

All writing