niblr

Custom CSS for your storefront

Style your Niblr storefront with your own CSS, written against a published set of data-nb-* attributes that survive platform updates. Covers the selector vocabulary, worked examples, what the validator strips on save, and why cart and checkout are excluded.

Every block on a Niblr storefront emits a stable set of data-nb-* attributes. Write your CSS against those and it survives platform updates — unlike CSS targeting utility classes or DOM structure, which can change on any release. Paste it into Store & branding → Custom CSS; it is validated on save and applied to your content pages.

Custom CSS is a Premium feature. The field is visible on every plan, but saving a stylesheet requires Premium.

Where do I put my CSS?

Store & branding → Branding → Custom CSS. One stylesheet per store, up to 32KB.

There is also a Custom CSS enabled toggle beside it. Turning it off disables your stylesheet without deleting it — useful if something looks wrong and you want the storefront back to normal while you work out what.

Your CSS is parsed when you save. If it does not parse, the save is rejected with an error rather than stored and silently half-applied.

What can I target?

Three attributes, on every block:

AttributeWhere it appearsValue
data-nb-blockThe block's wrapperThe block type — featureGrid, reviews, mediaHero, ctaBanner, and so on
data-nb-variantThe block's wrapperThe layout variant, when the block has one — cards, list, masonry. Absent entirely when no variant is selected
data-nb-partElements inside a blockA semantic role — see the vocabulary below

data-nb-block and data-nb-variant sit together on the wrapper around a block's output. data-nb-part sits on the individual elements it marks, wherever they land inside.

What are the part names?

Eleven, in two groups. Block-level parts mark a role the block has for itself. Item-level parts mark the same roles inside one entry of a repeated list.

PartLevelWhat it marks
headingBlockThe block's own title
bodyBlockThe block's own supporting text
mediaBlockThe block's own image or video
ctaBlockA call-to-action link or button
eyebrowBlockThe small label above a heading
badgeBlockA small chip carrying a number or status
linkBlockA textual link that is not a call to action
itemItemOne repeated entry — a card, a row, a tile
item-headingItemThe title inside a repeated entry
item-bodyItemThe supporting text inside a repeated entry
item-mediaItemThe image or video inside a repeated entry

A block can have both levels at once. A feature grid has one heading for the section and an item-heading on every tile beneath it — two different elements, two different selectors.

link or cta? Pick by role, not by tag. Both mark anchors. cta is the button-styled link driving the block's primary action. link is a plain textual link sitting inline in the copy — a phone number or email address in a contact block, or a small credit line inside a review. If you are unsure, ask whether it should look like a button or like text.

How do I style something inside a repeated item?

Combine item with the part name. There is deliberately no item-badge or item-linkitem already marks the repeated wrapper, so anything inside it is reached with a descendant selector:

[data-nb-part="item"] [data-nb-part="badge"] {
  background: var(--color-accent);
}

How do I target a block's colour band?

A fourth attribute, data-scheme, sits on the same wrapper and names which colour band the section renders on:

ValueWhat it means
defaultNo band — inherits the page background
mutedA subtle alternate surface, a slight step off the page background
inverseA full band in your primary brand colour, with contrast text
accentA full band in your accent colour, with contrast text

This is worth combining with data-nb-block, because the same part needs different treatment depending on the band behind it — a cta on a default section rarely wants the same styling as one on inverse.

What do real selectors look like?

Style every feature tile's title, across every variant of that block:

[data-nb-block="featureGrid"] [data-nb-part="item-heading"] {
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

Style review cards only in the masonry layout, leaving the grid and list layouts untouched:

[data-nb-block="reviews"][data-nb-variant="masonry"] [data-nb-part="item"] {
  border-radius: 0;
}

Style a call-to-action only where it renders on an inverse band:

[data-nb-block="ctaBanner"][data-scheme="inverse"] [data-nb-part="cta"] {
  box-shadow: 0 0 0 2px currentColor;
}

Will my CSS survive updates?

That is what the contract is for. It makes two promises.

A part name never changes meaning. heading will always be the element you would call the block's title, on any block, in any future markup. The contract is semantic, not structural — it promises the role exists, never that it lives on a particular tag or at a particular nesting depth. That is precisely what lets us rebuild a block's internals without breaking your stylesheet.

A block emits only the parts it has. An image gallery has no heading, so it never emits heading — only item and item-media. A spacer emits nothing at all. Absence is normal, not a bug. Write CSS that applies when a part is present; never write CSS that depends on it being there.

The contract is versioned nb-hooks/1. Adding a part name does not bump that version and cannot break you — a name that did not exist before cannot have been targeted before. Only renaming or removing a published name would be a breaking change.

What is not supported?

The contract is these attributes and nothing else.

  • Utility classes. Blocks are built with Tailwind internally and those class names appear in the HTML, but they are an implementation detail. Undocumented, unstable, and liable to change on any release including a patch.
  • Element and structural selectors. Do not target tag names (h2, section, img), nesting depth (div > div > p), or sibling order. None of it is covered by the promises above.
  • Our DOM shape generally. Wrappers and containers can be added, removed or rearranged at any time. If it is not a data-nb-* attribute or data-scheme, do not build on it.

If a selector you need is not expressible against this vocabulary, that is a gap in the vocabulary — tell us rather than reaching past it into internals, because anything you reach for there can move without warning.

What does the validator strip on save?

Your CSS is parsed and cleaned rather than stored untouched.

  • @import is removed. Pulling in an external stylesheet is an unbounded surface — it would let arbitrary remote CSS load on every storefront visit.
  • URLs are default-deny. Only three shapes are kept: a data: URI, a root-relative path beginning with a single /, and a same-document #fragment reference such as url(#gradient). Everything else is stripped, including absolute (https://…), protocol-relative (//example.com/…) and other relative forms (images/foo.png). This applies to every function taking a URL, not just url()image-set(), src() and image() are held to the same rule. Host images in your own Media library and reference them with a /-relative path.
  • Backslash escapes outside a string are refused. A declaration containing a backslash anywhere other than inside a quoted string is dropped whole, because an escape can disguise any of the rules above. content: "\2014" is fine; content: \2014 is not.
  • position: fixed is removed. A fixed element ignores the page's layout and can cover navigation or cart controls from anywhere in the stylesheet. position: sticky is kept.

One known rough edge: these rules read the raw declaration text, so the literal word url( inside a string — content: "see url(here)" — is treated as a URL and the declaration is dropped. That is the default-deny posture failing in the safe direction.

Does custom CSS apply to cart and checkout?

No, and deliberately.

Your CSS is applied to four route types: your store home page, your custom pages, the product listing, and product detail pages. The cart page and checkout pages receive none of it. Those carry the flow where a broken style could stop a customer completing a purchase, so they were kept out of scope from the start rather than protected afterwards.

The cart drawer is not the cart page, and it is styleable. The slide-out drawer renders on every route, so on the four content pages above your CSS reaches it — including its Checkout button. That is intentional; styling your own drawer is reasonable. But if you restyle it, check the Checkout button is still visible and clickable, because nothing stops you hiding it.

Where do I start?

Build your pages from content blocks first — the attributes above are emitted by those blocks, so there is nothing to target until a page has some. Then open your storefront, inspect a block, and you will see the data-nb-* attributes on it. Write your selectors against what you find there, and nothing else.

The rest of the documentation covers products, payments, domains and deployment.