"CSS: states, skeletons and variables"
What Smootify's stylesheet does, the state classes and page flags you can style, the skeleton placeholders and their variables, popover backdrops, upload classes and the CSS variables Smootify writes.
Smootify’s stylesheet is small and leaves your design alone. Most of what you style comes from Smootify in two forms: classes it adds to elements as their state changes, and CSS variables it writes or reads. This page lists what you can use in your own CSS.
The stylesheet
The integration adds the stylesheet next to the Smootify script, unless runtimeCssUrl is false: see Install Smootify in Astro. It holds:
- the
conditionrules: every[condition="…"],[customer-condition="…"]or[cart-condition="…"]element is hidden withdisplay: none !importantwhile its state does not match. Without the stylesheet, these elements always show; - the skeleton placeholders for elements with a
skeletonattribute (below); - a few defaults: closed popovers are hidden, popovers and the cart’s dialog lose the browser’s padding and border (your classes still set theirs), and the cart’s dialog, the market suggestion and the
store-availabilitydialog get a backdrop.
Your own CSS goes in the component’s scoped <style>, or in global.css. Smootify clones templates, and the clone keeps the component’s scope attribute, so scoped rules still match the state classes. Use :global() for HTML that Smootify writes from data, which has no scope attribute, and for classes it sets on a host:
<style>
smootify-product.is-on-sale .product-price {
color: #b42318;
}
:global(smootify-product.loaded) .product-info {
opacity: 1;
}
</style>See Write components with Smootify elements.
State classes
Smootify adds and removes classes as things change. The main ones:
- On
smootify-product:loaded,is-available/is-not-available,is-on-sale,is-free,is-new,is-variable,price-varies,has-subscription,not-existing-variant,is-currently-out-of-stock, and the stock levelsmany-in-stock,low-stock,low-not-last-stock,last-in-stock. - On the values of a picker:
is-activeon the selected value,is-disabledon a value that cannot be bought (it stays clickable). - On
smootify-cart:loaded,is-rendered,is-empty-cart,has-taxes,has-empty-quote. - On a product that Shopify does not return: the
hiddenattribute, on the card (.sm-dyn-item) or onsmootify-product. Smootify marks thehiddenit sets and removes only that one.
smootify-product.is-on-sale .product-price {
color: #b42318;
}
variant-swatches button.is-active {
outline: 2px solid currentColor;
}
variant-swatches button.is-disabled {
opacity: 0.4;
}Every element’s classes are in the Reference: state classes.
loaded
loaded goes on an element once Smootify has written it: products, prices, the cart, the account lists, sort and filter widgets, images and more. Use it for an entrance transition:
smootify-product .product-info {
opacity: 0;
transition: opacity 0.3s;
}
smootify-product.loaded .product-info {
opacity: 1;
}Page flags
Some states apply to the whole page, on html or body:
html[logged-in]andbody.is-user-logged-inwhile a customer is logged in;html[customer-status="loaded"]andbody.customer-status-loadedonce the session is known, logged in or not;body.has-customer-names,has-addresses/no-addresses,has-orders/no-orders,has-subscriptions/no-subscriptionsfor the account pages;body.has-wishlist,body.has-last-viewed,body.has-cart-upsellswhile those lists have products;has-related,has-complementary,has-best-sellerand the otherhas-<source>classes onhtmlandbody, while asmootify-productlist with thatdata-idhas products;body.cookie-banner-visiblewhile thesmootify-consentbanner shows.
html:not([logged-in]) .account-menu {
display: none;
}Skeletons
An element with a skeleton attribute is a placeholder until Smootify writes it. Smootify then removes the attribute, also when the value is missing and the element is hidden. Only elements with the attribute are placeholders.
skeleton="text","button"or"block": a pulsing background, transparent text and border, children invisible, not clickable;skeleton="image", on an Image: a pulsing blur;skeleton="hide": hidden (visibility: hidden), so it keeps its space.
Their colours and corner radius come from three CSS variables.
Customer values ([customer], [customer-condition], [company] and the customer metafields) pulse the same way until html[customer-status="loaded"], without a skeleton attribute. How to add skeletons is in loading.
Popovers and backdrops
The mini cart’s dialog, the market suggestion and the store-availability dialog get a backdrop, coloured by two CSS variables. The market suggestion and the store-availability dialog also blur the page behind, through the class with-blur, unless the element has disable-blur. Other popovers have no backdrop: set --smootify-backdrop-color on a panel’s class to give it one. The popovers inside that panel do not get it.
A closed popover is hidden with display: var(--smootify-closed-display, none) !important, so style its open state with :popover-open. To show a closed popover in the page at some breakpoints, set --smootify-closed-display on its class there. More in Popovers and Market suggestion.
CSS variables
Five variables change the look of skeletons and backdrops. Smootify writes the other four, for your CSS to read:
The first color of the pulse
- Default
hsl(200, 20%, 80%)
The second color of the pulse
- Default
hsl(200, 20%, 95%)
The corner radius of the placeholder
- Default
4px
The backdrop of a popover, set on the panel's class, and of the mini cart's dialog
- Default
- none on popovers;
rgba(0, 0, 0, 0.8)on the mini cart's dialog - Not passed
- to popovers inside the panel
A closed popover shown in the page, on the panel's class at the breakpoints you choose
- Default
none: a closed popover is hidden- Example
flexon desktop: a filter drawer becomes a column
The backdrop of the market suggestion and of the store-availability dialog
- Default
rgba(0, 0, 0, 0.1)
A rating's share of its scale
- Written on
- A
ratingorlist.ratingmetafield element. - Value
- A percentage, such as
90%.
The color of a color metafield
- Written on
- A color metafield element.
- Value
- The color.
How much of a file has uploaded
- Written on
- Each upload item and its progress element.
- Value
- From
0to1while the file uploads.
The width of the page's scrollbar
- Written on
html.- Value
- The width, measured at load.
A property="--my-name" attribute on a metafield element writes the rating or the colour into that variable instead. See Metafields on the page.
A fill inside a rating element with width: var(--metafield-rating) draws stars, and an upload’s progress element can scale with --upload-progress:
:root {
--skeleton-loading-start: #e4e4e7;
--skeleton-loading-end: #f4f4f5;
--skeleton-radius: 8px;
}
.filter-panel {
--smootify-backdrop-color: rgba(15, 23, 42, 0.6);
}
.stars-fill {
width: var(--metafield-rating);
}
[upload="progress"] {
transform: scaleX(var(--upload-progress, 0));
transform-origin: left;
}Upload classes
Upload fields (file-uploader, file-input, configurator-file-input, server plan) use your markup and add classes you style:
- on the element:
is-dragoverwhile a file is dragged over,is-uploading,has-files,is-fullwhen no more files fit,is-invalidwhen a file was refused; - on each item:
is-uploading,is-done,is-error,is-image.
The parts are in upload and File uploads.
Snippets
Short rules for common needs. Put them in global.css and change the styles to fit your design.
A tooltip on swatch buttons. Buttons that show only a colour or an image can show the value’s name on hover: every variant-swatches button carries its value in data-value.
variant-swatches button { position: relative; }
variant-swatches button:hover::before {
content: attr(data-value);
position: absolute;
bottom: calc(100% + 0.4rem);
left: 50%;
transform: translateX(-50%);
z-index: 10;
padding: 0.2rem 0.5rem;
border-radius: 0.3rem;
background: black;
color: white;
font-size: 0.8rem;
white-space: nowrap;
}No shimmer on a cart line that changes. While its quantity changes or it is removed, a line gets the class loading and a pulsing layer over it. To keep the line still (it still ignores clicks until the change is done):
cart-item.loading::before,
cart-item.loading bundle-item > *::before { display: none !important; }A checked checkbox or radio button. To style the whole label, text included, when its input is checked:
label:has(input:checked) { /* your styles */ }The page while a menu is open. A menu is a [popover] or a <dialog>. To change the page behind it, for example to stop it from scrolling:
body:has(:popover-open, dialog[open]) { overflow: hidden; }Where people get stuck
Check these first
- Elements with a condition always show. The stylesheet is not loaded: do not set
runtimeCssUrl: falseunless the site has those rules itself. - Nothing pulses while the page loads. In 2.0 only elements with a
skeletonattribute are placeholders. Add it where you want one.