Write components with Smootify elements
Build .astro components with Smootify's elements and attributes, and the plain-HTML markup that replaces dropdowns, sliders, tabs and form messages.
A Smootify component is an .astro file that writes Smootify’s elements and attributes as plain HTML. Smootify reads the HTML at load, so the same elements and attributes work as on any Smootify site. This page covers the rules of the markup, the parts that need a plain-HTML form, and how to style them.
Note
The elements, attributes and their values are the same everywhere: see Build the cart and the other Build pages for each one. This page is about how they are written in Astro.
1. Elements and attributes
A Smootify element is a tag, such as smootify-product, smootify-cart or smootify-add-to-cart. Its parts are attributes on ordinary elements, such as product="title", cart="subtotal" or data-action="remove". Write them in the markup:
---
const { p } = Astro.props;
---
<div class="sm-dyn-item">
<smootify-product data-id={p.shopifyId}>
<a href={`/products/${p.id}/`}>
<img src={p.image} alt={p.imageAlt} width={p.imageWidth} height={p.imageHeight} />
<h3 product="title">{p.name}</h3>
<div product="price">{p.priceFormatted}</div>
</a>
</smootify-product>
</div>- Every card of a product list has the class
sm-dyn-item, the boundary of the card, and inside it asmootify-productwithdata-idset to the product’sshopifyId. The build reads that id: see First load. - Render what the entry knows, such as the title, the picture and the price, in the markup. They are also the HTML that search engines read.
- Do not hand-write what Smootify draws from the variant: prices after a variant change, stock messages, selected option labels.
astro checkknows the attributes: the integration adds their types. If it rejects an attribute that exists, do not cast it. The types are missing it: report it.- Conditions that depend on the page’s data are Astro expressions (
{p.compareAtPrice > p.price && …}). Conditions that depend on the visitor or on stock stay attributes (condition="out-of-stock",customer-condition). - A tag that Smootify’s reference calls a marker, such as
search-with-query, is an element, not an attribute.
2. Props
Props are the component’s Props, in camelCase, with a default. A variant is a variant prop that adds the class is-<variant>. A part the caller fills is a <slot />.
---
interface Props {
/** Shows the wishlist heart */
showWishlist?: boolean;
variant?: "default" | "compact";
}
const { showWishlist = true, variant = "default" } = Astro.props;
---
<div class:list={["sm-card", `is-${variant}`]}>
<slot />
{showWishlist && <wishlist-toggle></wishlist-toggle>}
</div>3. Plain HTML instead of native widgets
Smootify’s runtime reads standard HTML. Use these forms.
| You need | Write |
|---|---|
| A dropdown | A [popover] with its trigger button popovertarget. The Smootify tags are sort-popover, filter-popover, variant-popover, addon-popover and configurator-popover. |
| A hidden part | The hidden attribute. The stylesheet hides [hidden] with display: none !important. |
| A form’s success and error message | [form-done] and [form-fail], each with hidden, next to the <form>. |
| A slider | [slider-track] (its children are the slides), [slider-prev], [slider-next] and [slider-dots]. |
| Tabs | WAI-ARIA tabs keyed by data-tab. |
| A lightbox | a[lightbox-item] and a viewer component. |
| A checkbox or radio that Smootify repeats | <label><input type="checkbox"><span form-label>Label</span></label>. |
| A language switcher | The [locales] element, with an a[hreflang] link for each locale. |
| A menu or a drawer | A [popover] or a <dialog> with data-overflow-body. |
| A card boundary | .sm-dyn-item on the card. |
A few rules go with them:
- Popovers. The open and close transitions are CSS:
@starting-styleandtransition-behavior: allow-discrete. The current option getsaria-current="true"and the triggeraria-expanded. A popover inside a template that Smootify repeats gets its ownidin each copy; on cards rendered on the server, give each popover its ownid. Insidesmootify-cart, keep popovers out of the lines (see Popovers). - Sliders. Smootify runs
[slider-prev],[slider-next]and[slider-dots]only in media sliders (gallery="slider"and product videos) and in Magic Box. A slider of product cards or of other content uses the site’s own slider script. hidden. Smootify marks what it hides with its own attribute. Never write that one, and never writehiddenon something Smootify should show, such as a filter widget or the load more button. Hide those with CSS on the host’s state class.- Form messages. A form that Smootify does not handle, such as a contact form, shows its own
[form-done]and[form-fail]from the response of itsfetch. A cart’s[form-done]is its empty state. - Locales. On a site with
locales, the switcher is a[locales]element of real links, one for each page in the other language or market, built withlocaleSwitcherLinks. Do not usecountry-switcherthere: it changes the country without changing the page, so the page and the visitor’s choice would disagree. - Search and discovery. Render the real product cards on the server. Smootify takes the first
.sm-dyn-itemas the template and rewrites the list from the results. Render onlylimitcards, in the collection’s order.
4. After Smootify renders
When Smootify renders or clones something, it fires smootify:render on document, at most once every 400 ms. event.detail.nodes is always an empty array, so a script that wires its own behaviour, such as an animation on product cards, listens to the event, queries the nodes it has not wired yet and marks them:
<script>
document.addEventListener("smootify:render", () => {
for (const node of document.querySelectorAll(".sm-card:not([data-wired])")) {
node.setAttribute("data-wired", "");
// wire the node
}
});
</script>Make the binding safe to run again: the event fires at every render. Sliders, tabs and popovers need no listener.
5. Styles
- A component’s styles go in its scoped
<style>. Smootify clones templates, and the clone keeps the component’s scope attribute, so scoped rules, including state classes such as.empty-count, still match. - Use
:global()for HTML that Smootify writes from data, which has no scope attribute: rich text,set:html, and classes that Smootify sets on a host (:global(.host.inited) .part). - A class that many components write, such as a button or a text style, lives in
global.css. - Smootify’s custom tags are inline by default: give them a
displaywhen they carry layout. - Where Smootify only hides what it must, the look of a state is yours: write it from the host’s state classes, such as
is-openoris-empty. - A template
imgwith no data has nosrc. Leave the attribute out, and hide the image withimg:not([src]) { visibility: hidden }. - Popovers keep two variables:
--smootify-closed-displayand--smootify-backdrop-color.
6. Examples
The example project for Smootify for Astro has components for each part of a store, in folders by what they do:
product/has the add to cart form, the card, the price, the gallery, the quantity, the stock messages and the wishlist button;variant/has colour swatches, pill swatches and the variant popover;cart/has the cart drawer and the line template, with the empty state as[form-done];collection/andfilters/have the sort popover, the filters and the active filters of search and discovery;navbar/,layout/andconsent/have the navbar with predictive search, the footer and the cookie banner;markets/has the country switcher, andserver/has a newsletter form with[form-done]and[form-fail].
Each file starts with a comment that says what it builds and where it goes.
Before you publish
Check these first
- A dropdown never opens. A
*-dropdowntag looks for a native dropdown that does not exist here. Use the*-popovertag with a[popover]and abutton popovertarget. - A part stays hidden. It has
hiddenthat Smootify does not remove. Remove it, and hide with CSS on the host’s class instead. - A script works on the first cards only. It runs once at load. Bind again on
smootify:render. - A cloned card loses its style. The rule is in a
<style is:global>that depends on markup, or targets HTML that Smootify wrote. Keep scoped rules for templates and use:global()only for written HTML. - The list reshuffles at load. The server cards are not in the collection’s own order, or there are more of them than
limit. - Prices do not match after load. A value is written by hand. Render the title and image, and let Smootify draw prices and states.
- The language switcher changes the price but not the page. It is a
country-switcher. On a site withlocales, use[locales]with links.