2.0 Beta

Smootify 2.0 is in public beta. Your 1.x store keeps working. Move when you want.

What changed

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.

v2.0 beta
Open .md
On this page
  1. 1. Elements and attributes
  2. 2. Props
  3. 3. Plain HTML instead of native widgets
  4. 4. After Smootify renders
  5. 5. Styles
  6. 6. Examples
  7. Before you publish

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 a smootify-product with data-id set to the product’s shopifyId. 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 check knows 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 needWrite
A dropdownA [popover] with its trigger button popovertarget. The Smootify tags are sort-popover, filter-popover, variant-popover, addon-popover and configurator-popover.
A hidden partThe 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].
TabsWAI-ARIA tabs keyed by data-tab.
A lightboxa[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 switcherThe [locales] element, with an a[hreflang] link for each locale.
A menu or a drawerA [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-style and transition-behavior: allow-discrete. The current option gets aria-current="true" and the trigger aria-expanded. A popover inside a template that Smootify repeats gets its own id in each copy; on cards rendered on the server, give each popover its own id. Inside smootify-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 write hidden on 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 its fetch. 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 with localeSwitcherLinks. Do not use country-switcher there: 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-item as the template and rewrites the list from the results. Render only limit cards, 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 display when 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-open or is-empty.
  • A template img with no data has no src. Leave the attribute out, and hide the image with img:not([src]) { visibility: hidden }.
  • Popovers keep two variables: --smootify-closed-display and --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/ and filters/ have the sort popover, the filters and the active filters of search and discovery;
  • navbar/, layout/ and consent/ have the navbar with predictive search, the footer and the cookie banner;
  • markets/ has the country switcher, and server/ 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 *-dropdown tag looks for a native dropdown that does not exist here. Use the *-popover tag with a [popover] and a button popovertarget.
  • A part stays hidden. It has hidden that 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 with locales, use [locales] with links.

    ↑ ↓ to moveEnter to openEsc to close