Make a tweak you can adjust from the theme editor (with a slider) — 3 levels

A lot of the answers we all give (mine included) are “paste this code and it works.” And it does — until you want to change the value. Then you’re back in the code editor every single time. Here’s how to take the exact same fix one step further, so you set it once and adjust it forever from the theme editor — no code re-runs.

I’ll use one example that comes up constantly: adding an image overlay (a dark/tinted layer so text stays readable over a photo) to a section that doesn’t have that control natively. The built-in Image banner HAS an opacity slider — multicolumn, collection and custom image sections usually don’t. The same idea applies to spacing, font sizes, button colours, show/hide badges, and more.

LEVEL 1 — Custom CSS (fastest, but frozen and global)
The usual answer: drop CSS into your theme code — theme.liquid or the theme’s main stylesheet:

.multicolumn-card__image-wrapper { position: relative; }
.multicolumn-card__image-wrapper::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.4);
}

It works — but it hits EVERY matching element on the site, the 0.4 is hardcoded, and to change it you edit code again. Fine for a one-off, painful to maintain.

LEVEL 2 — Custom Liquid block (scoped, but still hardcoded)
Add a Custom Liquid block and scope the style to that one instance with its id, so it stops leaking onto every card:

<div class="overlay-{{ block.id }}">
  <!-- your image / content -->
</div>
{% style %}
  .overlay-{{ block.id }} { position: relative; }
  .overlay-{{ block.id }}::after {
    content: ""; position: absolute; inset: 0;
    background: rgba(0, 0, 0, 0.4);
  }
{% endstyle %}

Better — no theme-file edits, and it only affects this block. But the Custom Liquid box has no settings of its own, so 0.4 is still frozen in code. This is where most “paste this” solutions stop.

LEVEL 3 — A section with its own settings, so the value becomes an editor slider
This is the Online Store 2.0 way: put the value in the {% schema %} as a range setting, and it turns into a slider in the editor. Create a section file at sections/image-with-overlay.liquid (Edit code > Sections > Add a new section):

{% style %}
  #Overlay-{{ section.id }} { position: relative; display: grid; place-items: center;
    min-height: {{ section.settings.min_height }}px; overflow: hidden; }
  #Overlay-{{ section.id }} .overlay-media { position: absolute; inset: 0;
    width: 100%; height: 100%; object-fit: cover; }
  #Overlay-{{ section.id }}::after { content: ""; position: absolute; inset: 0;
    background: {{ section.settings.overlay_color }};
    opacity: {{ section.settings.overlay_opacity | divided_by: 100.0 }}; }
  #Overlay-{{ section.id }} .overlay-content { position: relative; z-index: 1;
    color: {{ section.settings.text_color }}; text-align: center; padding: 2rem; }
{% endstyle %}

<div id="Overlay-{{ section.id }}">
  {% if section.settings.image %}
    {{ section.settings.image | image_url: width: 2000 | image_tag:
       class: 'overlay-media', loading: 'lazy', widths: '600,1000,1500,2000' }}
  {% endif %}
  <div class="overlay-content">
    {% if section.settings.heading != blank %}<h2>{{ section.settings.heading }}</h2>{% endif %}
    {{ section.settings.text }}
  </div>
</div>

{% schema %}
{
  "name": "Image with overlay",
  "settings": [
    { "type": "image_picker", "id": "image", "label": "Image" },
    { "type": "range", "id": "overlay_opacity", "min": 0, "max": 100, "step": 5, "unit": "%", "label": "Overlay opacity", "default": 40 },
    { "type": "color", "id": "overlay_color", "label": "Overlay colour", "default": "#000000" },
    { "type": "color", "id": "text_color", "label": "Text colour", "default": "#ffffff" },
    { "type": "range", "id": "min_height", "min": 200, "max": 800, "step": 20, "unit": "px", "label": "Section height", "default": 400 },
    { "type": "text", "id": "heading", "label": "Heading", "default": "Your heading" },
    { "type": "richtext", "id": "text", "label": "Text" }
  ],
  "presets": [{ "name": "Image with overlay" }]
}
{% endschema %}

Three things that make it show up and work in the editor:

  • “presets” is what makes the section appear under Add section — leave it out and it never shows up (the #1 “why can’t I see it” cause).
  • Every entry under “settings” becomes a control in the section panel: range = slider, color = colour picker, text/richtext = text fields.
  • range returns a number 0–100, so divide by 100 for CSS opacity: overlay_opacity | divided_by: 100.0 (use 100.0, not 100, or Liquid floors it to 0).
  • Scoping the CSS to #Overlay-{{ section.id }} means you can add the section ten times with ten different overlays — no collisions.

Now the merchant drags “Overlay opacity” in the editor and never touches code again. (Advanced: the same schema works as a theme block in /blocks/ — add “presets” there too, and drop it into any section that has “blocks”: [{ “type”: “@theme” }] plus {% content_for ‘blocks’ %}, referencing block.settings.…)

The takeaway: whenever you’re about to paste a fixed value into a theme — a spacing, a font size, a colour, an opacity, a show/hide toggle — ask “should this be a setting instead?” If it’ll ever change, the few extra minutes to make it a range/checkbox/color save both you and the merchant every future edit.

Happy to help adapt this to your specific theme or section — just paste what you’re working with.

I always find it cool to set up a little custom block/section, so they can change it easily themselves in the theme editor. A bit like “look, you’ve invested in your store by giving me work, so here’s something to remember me by”.

Great for you to share!

Ha — “something to remember me by” is a perfect way to put it. Funny thing is, my favourite outcome is when they don’t need to remember me: they open the theme editor months later, nudge the slider, and it just works — no ticket, no “can you change this back for me?”. That quiet “oh nice, I can just do this myself” tends to earn more goodwill (and referrals) than my name sitting in a code comment ever would.

So fully with you — it’s a nice little signature to leave behind, whether they notice it or not. Thanks for the kind words, glad it landed! :slightly_smiling_face:

Most of the people in here are just copy/pasting the post into Chatgpt or something and pasting the results, not actually doing the work and verifying that it’s a correct solution. So you’re “give them a fully editable theme block” isn’t going to work on 80% of the responses here.

The rest of us aren’t here to be building out someone’s theme they got for free and making it premium, most probably don’t have the time. For private interaction, I definitely agree. Free forum help, I think it would be unhealthy and create an imbalance between free and paid theme customization. But be my guest. If you think you want to spend that much time on fixing a font size issue, who are we to say you shouldn’t.. I think you will become a very great help in here. I don’t agree with the principle but I’ll agree to support your endeavor.

The ChatGPT thing is actually my whole point: an unverified copy-paste snippet is exactly what breaks two weeks later and becomes the next thread. Something editable/native in the theme editor is the opposite — the merchant owns it, tweaks it themselves, and doesn’t come back for the same font-size question.

And I’m not suggesting anyone rebuild a free theme into a premium one for free — nobody has time for that, and that’s not what I’m on about. It’s about how you hand over a fix, not doing more of them.

Either way — appreciate the honest pushback, and the support despite the disagreement. That’s the good kind of forum energy.

After a bit they don’t even have to do as it will just be scraped as training data.
Or the browser or an extension makes it a 1click button, or grabs code blocks automatically.
Or they could just ask the sidekick agent.

THEN something goes wrong and the free support search spiral starts.

With some exceptions that’s self defeating when combined with not putting no attribution or contact info anywhere in the code itself or the settings themselves.
It’s one thing to provide free labor, another for entirety of working code, and yet another to set yourself up to be forgotten.
How are they gonna refer someone they’ve forgotten.

Especially in an entrepreneurial space overflowing with manufactured getrichquick mentalities and middlemen masquerading as merchants where your very likely setting yourself up to be providing support for somone selling your code.
exceptions being having tons of runway|capital to burn on loss leader marketing, your a charity, or it’s AI-generated code so it’spublic domain anyway

{ "type": "paragraph", "content": "© 2026 ABC website.url - featurename versionX" }

or further use headers, or things like mailto links with filled in title for the feature in the paragraph.

Fair point — you’re right, and it’s a better-calibrated take than mine. “Happy to be forgotten” only really holds if you’re not trying to build a referral engine off it, and most of us are. Point taken.

The way I square it: a light attribution that lives where the next developer looks, not where the shopper does. A comment in the code or the {% schema %} (“built by … · contact”) survives the copy-paste and does the referral job — without stamping branding onto the merchant’s storefront UI, which on paid client work they usually don’t want anyway.

Your ©/paragraph example is the visible end of that; for free forum snippets I’d lean to a Liquid comment + a mailto, so it’s there for whoever inherits the code but invisible to customers. And the “free support for someone reselling your code” trap is real — a version tag in that comment at least tells you which vintage of your code broke when the spiral starts. :slightly_smiling_face: