Skip to content

Data Attributes

Most of what a marketing site wants from Open Shop is small: hide the Login link when someone is signed in, print their first name, open the cart from a nav button. The data-os-* attributes do those without a line of JavaScript.

Add the attribute to your own HTML. The bundle reads it and acts, using the same JavaScript API underneath. Your site builder does nothing with these attributes beyond publishing them into the markup.

Site nav
<a href="/customer" data-os-show="guest">Login</a>
<a href="/customer" data-os-show="customer">Hi, <span data-os-text="customer.firstName"></span></a>
<span data-os-show="referral">Shopping with <span data-os-text="referral.firstName"></span></span>
<div data-os-hide="cart">Free shipping on your first order</div>
<a href="/assessment/" data-os-handoff>Take the Assessment</a>
<button data-os-cart-open>Cart (<span data-os-text="cart.itemCount">0</span>)</button>
<button data-os-add-to-cart="vip-perks">Subscribe</button>
AttributeBehaviour
data-os-show="guest | customer | referral | cart"Sets hidden on the element unless the state matches. cart means the cart holds at least one item. Comma-separated values are OR’d: data-os-show="customer,referral" shows for either.
data-os-hide="guest | customer | referral | cart"The mirror of data-os-show: sets hidden when a listed state does match. Same four values, also OR’d.
data-os-text="<key>"Writes the value into the element’s textContent. Keys come from the whitelist below; anything else leaves the element untouched.
data-os-handoffOn click, mints a handoff token and sends the shopper to the anchor’s href with ?token= appended.
data-os-cart-openOn click, opens the cart slideout.
data-os-cart-closeOn click, closes the cart slideout.
data-os-add-to-cart="<slug>"On click, adds that product to the cart. Errors surface through the bundle’s own toasts.
data-os-quantity="<number>"Optional companion to data-os-add-to-cart. How many to add.
data-os-auth="pending | guest | customer"Set by the bundle on <html>, never by you. The CSS-only version of data-os-show.

The pattern is a pair of elements, not one element that flips. Build both states in your design, mark each with the state it belongs to, and let Open Shop hide the wrong one.

Site nav
<a href="/customer" data-os-show="guest">Login</a>
<a href="/account" data-os-show="customer">My Account</a>
ValueShown when
guestLogin state resolved and nobody is signed in.
customerLogin state resolved and someone is signed in.
referralAn enroller is resolved for this visit.
cartThe cart holds at least one item.

Neither login variant shows while login state is still resolving, so the nav does not flash the wrong link. See data-os-auth for styling that in-between moment yourself.

One moment the bundle cannot cover is the one before it runs: the browser can paint your markup as written before a deferred script executes, so on a slow first visit both links would appear for an instant. One line of site CSS closes that gap by hiding every data-os-show element until the bundle’s first paint, which is also when <html> first gets its data-os-auth attribute:

Site custom CSS
html:not([data-os-auth]) [data-os-show] {
display: none;
}

A value the bundle does not recognise leaves the element exactly as you authored it, so a typo like data-os-show="custmer" shows the element rather than hiding it. Mistakes fail visible, never blank.

The mirror of data-os-show. It takes the same four values, comma-separated and OR’d, and hides the element when any listed state matches. When none match, the element shows.

Site nav
<div data-os-hide="cart">Free shipping on your first order</div>
ValueHidden when
guestLogin state resolved and nobody is signed in.
customerLogin state resolved and someone is signed in.
referralAn enroller is resolved for this visit.
cartThe cart holds at least one item.

A value the bundle does not recognise leaves the element untouched, exactly as with data-os-show.

guest and customer in a hide list match nothing until the customer resolves, so data-os-hide hides nothing on their account during that window. This is deliberately the opposite of data-os-show, which hides both variants while pending.

The practical consequence: a data-os-hide="customer" element starts visible and disappears once a signed-in shopper resolves. That is fine when a brief appearance is harmless, and wrong when it is not.

Use data-os-show pairs for the login link. A hide-based nav would show the wrong link during the pending window — a signed-in shopper would see Login until their session resolved, which is the flash the pair pattern exists to prevent.

Use data-os-hide for content that should be visible by default and step aside once a state applies:

MarkupWhat it does
data-os-hide="cart"Hides a promo banner once the cart has something in it.
data-os-hide="referral"Hides an enroller-finder block once an enroller is attached.
data-os-hide="customer"Hides a “create an account” nudge from signed-in shoppers.

That last one is safe precisely because the pending window is harmless: a signed-in shopper may see the signup nudge for a moment before their login state resolves, which costs nothing. Apply the same test to your own case — if a brief appearance would be wrong, use a show pair instead.

An element carrying both attributes is hidden if its show list fails or its hide list matches. Both tests must pass for it to show.

Shown only to signed-in shoppers with an empty cart
<div data-os-show="customer" data-os-hide="cart">Ready to reorder?</div>

Both data-os-show and data-os-hide hide an element the same way: the bundle sets the hidden attribute on it, and ships this rule inside its os-components cascade layer:

What the bundle injects
[data-os-show][hidden],
[data-os-hide][hidden] {
display: none !important;
}

That rule exists because hidden alone is fragile: any display value on the element overrides it, and site builders set one on almost every nav link. Webflow gives links display: flex or inline-block as a matter of course, which would otherwise un-hide them.

An !important declaration inside a cascade layer outranks everything in unlayered CSS — your normal rules and your !important ones, whatever their specificity. So the element stays hidden no matter what display your classes set. You do not need to do anything to make hiding work.

Writes a single value into the element. The whole reachable surface is this whitelist — it is not a path evaluator, so customer.id or cart.items[0].name do nothing.

KeyValue
customer.firstNameSigned-in shopper’s given name.
customer.lastNameSigned-in shopper’s family name.
customer.accountTypeNameTheir account type.
customer.priceTypeNameTheir price type.
referral.firstNameResolved enroller’s given name.
referral.lastNameResolved enroller’s family name.
referral.handleResolved enroller’s referral handle.
cart.itemCountNumber of items in the cart.
cart.subtotalCart subtotal.
store.nameThe store’s name.

Put a sensible fallback inside the element and it shows until the real value arrives:

Site nav
<button data-os-cart-open>Cart (<span data-os-text="cart.itemCount">0</span>)</button>

Put it on an anchor that points at another app your store hands shoppers off to. On click, Open Shop mints a handoff token and navigates to the href with ?token= appended, so the shopper does not sign in again.

Site nav
<a href="/assessment/" data-os-handoff>Take the Assessment</a>

The attribute needs no value — its presence is the switch.

It falls through to the plain href — the ordinary link, no token — whenever a token is not the right answer:

  • The shopper is a guest.
  • Minting the token fails.
  • The click is a modified click (⌘, Ctrl, Shift, Alt) or a middle click, so “open in a new tab” keeps working.

A shopper the bundle already knows is a guest is not intercepted at all — the browser follows the link natively, so target="_blank", rel, and every other link behaviour work exactly as authored.

For a signed-in shopper, and for anyone whose login state has not resolved yet, the bundle intercepts the click, mints the token, and then navigates in the same tab. target="_blank" is not honoured on that path. Do not rely on a handoff link opening a new tab: a guest would get one and a signed-in shopper would not.

Site nav
<button data-os-cart-open>Open cart</button>
<button data-os-cart-close>Keep shopping</button>
<button data-os-add-to-cart="vip-perks" data-os-quantity="2">Subscribe</button>

data-os-cart-open and data-os-cart-close need os-cart-slideout on the page to have something to open.

data-os-add-to-cart takes the product slug. A successful add opens the cart slideout, the same as the components’ own add-to-cart buttons. Failures — an ineligible product, a duplicate enrollment — are reported by the bundle’s own toast, again the same way the product components report them. If you need to handle the failure yourself, call OpenShop.cart.add() instead and catch the error.

The bundle sets data-os-auth on the <html> element and keeps it current. You never write it; you style against it.

ValueMeaning
pendingLogin state has not resolved yet.
guestResolved: nobody is signed in.
customerResolved: a shopper is signed in.
Site custom CSS
html[data-os-auth='customer'] .nav-login {
display: none;
}

This is the CSS-only twin of data-os-show, and it is the tool for the in-between moment. Write the rules as “hide until the state that shows this is known” rather than matching pending: before the bundle has run at all, <html> carries no attribute, and :not() covers that window too.

Site custom CSS
html:not([data-os-auth='guest']) .nav-login,
html:not([data-os-auth='customer']) .nav-account {
visibility: hidden;
}

In practice pending is brief, and on repeat page views it is usually skipped entirely — the last known state is restored from the session cache before the request comes back.

  • Clicks are delegated. One listener on document handles every click attribute, so elements your own scripts insert later work with no extra setup.
  • Bindings are scanned at ready, then again on every customer:change, cart:change, and referral:change, and once more when the store overview resolves — that last pass is what fills store.name. Site-builder pages are static markup, so that covers them. If your code injects data-os-show, data-os-hide, or data-os-text markup after those events, nothing rescans it — call the JavaScript API directly for that content instead.
  • Values are written with textContent. Never innerHTML.
  • Keys come from a whitelist. Unknown keys are ignored and leave the element exactly as you authored it.

Webflow calls these custom attributes. They live on the element, not in an embed, so you can add them to links and buttons you already designed.

1 In the Webflow Designer, select the element you want to control — a nav link, a button, a text span.

2 Open the Element settings panel on the right (the gear icon above the Style panel).

3 Scroll to Custom attributes and click the +.

4 Type the attribute in Name and its value in Value, then press the checkmark to save:

GoalNameValue
Show this link only to guestsdata-os-showguest
Show this link only to signed-in shoppersdata-os-showcustomer
Hide this banner once the cart has itemsdata-os-hidecart
Print the shopper’s first namedata-os-textcustomer.firstName
Open the cart from this buttondata-os-cart-openleave empty
Hand off a signed-in shopperdata-os-handoffleave empty

5 Repeat for each element, then Publish. Custom attributes only reach the page on a published site — the Designer canvas will not run them.

For a full walkthrough that puts this into a real nav, see Step 6 of the Webflow tutorial.