Docs
Dashboard
View as Markdown
On this page
  1. Mode
  2. Tokens
  3. The accent ramp
  4. Contrast
  5. Fonts
  6. Rules
  7. What is refused
  8. The checks after your rules
  9. The class names
  10. Starting from nothing
  11. Changing the brand later

Styling Elements

Mode, tokens with a full accent ramp, Google Fonts, rules scoped to a published class list, preset bare, and restyling live.

Elements paint no background of their own: they take whatever is behind them, so a checkout in a white card looks like part of that card. Everything else comes from brand:

const elements = shopfast.elements({
  items,
  brand: {
    mode: "light",
    fonts: ["Fraunces:wght@500;700", "Inter:wght@400;500;600"],
    tokens: { accent: "#b2452a", text: "#221f1c", border: "#ddd5c9", radius: "4px", font: "Inter, system-ui, sans-serif", fontHeading: "Fraunces, Georgia, serif" },
    rules: {
      ".sf-el-pay": { borderRadius: "999px", letterSpacing: "0.02em" },
      ".sf-el-method[data-selected]": { backgroundColor: "#fbf3f0" },
    },
  },
});

Try every token, font and rule live in the playground: it shows the code as you go.

Mode

mode is "light", "dark" or "auto" (the default, which follows the buyer’s system setting). Set it when your page is always light or always dark, or a dark-mode visitor gets a dark checkout on your white page.

Tokens

Tokens are the broad strokes. Each one becomes a CSS variable every element reads.

Token Variable What it styles
accent --sf-el-accent Buttons, the selected method, focus rings and links. The whole ramp comes from it.
onAccent --sf-el-on-accent Text on the accent. Picked for contrast when you leave it out.
text --sf-el-text Body text, labels and amounts.
textMuted --sf-el-text-muted Hints, captions and secondary text.
border --sf-el-border Field borders, dividers and method cards.
surface --sf-el-surface Behind each element. Transparent unless you set it.
surfaceSunken --sf-el-surface-sunken Quiet panels: the test card, the totals box, chips.
input --sf-el-input Inside fields and selects.
danger --sf-el-danger Errors and invalid fields.
success --sf-el-success Savings, applied codes and the paid state.
warning --sf-el-warning Test mode and notices.
radius --sf-el-radius Corner radius of fields, buttons and cards (0 to 32px).
gap --sf-el-gap Space between rows and sections (4 to 40px).
fontSize --sf-el-font-size Base text size (12 to 20px).
font --sf-el-font Body font stack.
fontHeading --sf-el-font-heading Headings and the total. Defaults to font.
fontMono --sf-el-font-mono Codes and the test card.

Colors take hex, rgb(), hsl() or oklch(). Lengths take px or rem. A value that does not fit is refused with a trace, and the default stays.

The accent ramp

Set accent and elements build eleven steps from it (--sf-el-accent-50 to --sf-el-accent-950) in OKLCH, so hover states, the selected method’s tint, focus rings and links all come from your color rather than a generic blue. The step closest to your accent is your accent exactly; the others keep its hue and are mapped into what screens can show.

Contrast

Elements check text against your colors with the WCAG 2 formula. Leave out onAccent and they pick white or near black, whichever reads better on your accent. When text on the accent, or text on a solid surface, falls under 4.5:1 (WCAG AA), you get a trace with the ratio:

Text on the accent is 1.98:1, below WCAG AA (4.5:1). Pick a darker or lighter accent.

Fonts

An element is its own document, so the fonts your page loads do not reach it. Load them into the elements:

brand: {
  fonts: [
    "Inter:wght@400;500;600",
    { family: "Fraunces", weights: [500, 700], italic: true },
    "https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@700",
  ],
  tokens: { font: "Inter, system-ui, sans-serif" },
}

Google Fonts only, up to four families, and the address is rebuilt from the family names and weights rather than used as you pass it: any other stylesheet would reach a payment page without going through the rules below. Then name the family in font, fontHeading or fontMono.

Rules

rules is CSS for the published class names, and only those:

rules: {
  ".sf-el-button": { borderRadius: "2px" },
  ".sf-el-field-input:focus-visible": { boxShadow: "0 0 0 3px rgb(178 69 42 / .25)" },
  ".sf-el-method[data-selected]": { borderColor: "#b2452a", backgroundColor: "#fbf3f0" },
  ".sf-el-total-amount": { fontFamily: "Fraunces, Georgia, serif" },
}
  • Selectors are one class from the list below, optionally with one state, [data-selected], [data-disabled], [data-loading], [data-invalid], [data-locked], [data-open], [data-empty], [data-applied], [data-checked] or [data-test], and one of :hover, :focus, :focus-visible, :focus-within, :active, :disabled, :checked, or ::placeholder on .sf-el-field-input. Several selectors may share a rule with commas. No element names, ids or combinators.
  • Properties may be written borderRadius or "border-radius". Numbers get px where CSS needs a unit.
  • Hover rules only apply on devices with a mouse, so phones never get a stuck hover.
  • !important is removed. Your rules already win over the defaults.
  • var() may read the elements’ own tokens (var(--sf-el-accent)), nothing else.

What is refused

A checkout that can be restyled freely can be made to lie about what it charges: hide the total, cover the pay button, dress a decline up as a success. So these are refused everywhere:

Refused Why
position, inset, top, right, bottom, left, float Can move parts out of place
z-index Can stack one part over another
content, quotes, counters Can add text that is not the checkout’s own
pointer-events Can make buttons ignore or pass on clicks
transform, translate, rotate, scale, zoom Can move, flip or shrink parts
clip, clip-path, mask and -webkit-mask Can cut parts out of view
filter, backdrop-filter, mix-blend-mode Can hide or recolor parts
overflow Can cut off the total or the pay button
direction, unicode-bidi, writing-mode Can reorder the characters of a price
text-indent, text-overflow, line clamps Can push text out of view
animation, all, appearance, behavior, -moz-binding Run code or reset every protection at once
Variables (--x: …) Use tokens instead
Any value with url(), image(), image-set(), expression(), attr(), env(), @, \, comments, ; or braces Fetches something, runs code or smuggles in more CSS
Lengths under −32px or over 4000px Pull parts out of the frame

Totals, the pay button, prices and the legal texts are protected (marked in the class list). They take colors, borders, radius, padding, shadows and type, and nothing that could hide them: no opacity, visibility, display, size or margins, text no smaller than 12px, solid colors only, and text at 3:1 contrast or better against its background.

Every refusal is a trace, never a silent drop:

elements.on("trace", (e) => console.warn(e.message));
// Rule ".foo" refused: .foo is not a ShopFast Elements class.
// .sf-el-pay { position } refused: position can move parts of the checkout out of place.

The checks after your rules

After your rules apply, each element checks its protected parts in the browser: still visible, inside the element, not see-through, at least 10px and readable against their background. If one fails, that element turns your rules off and says why:

Rules turned off in the payButton element: .sf-el-pay cannot be read against its background with them.

The class names

These are a promise: once a class is on this list it keeps existing and keeps meaning the same thing, even when what is behind it is rewritten. New classes are only ever added.

Group Class Protected What it is
Roots .sf-el-checkout The all-in-one checkout element.
Roots .sf-el-summary The cart summary element.
Roots .sf-el-email The email element.
Roots .sf-el-payment The payment method element.
Roots .sf-el-coupon The coupon and gift card element.
Roots .sf-el-consent The terms, marketing and withdrawal element.
Roots .sf-el-billing The billing details element (billingAddress).
Roots .sf-el-custom-fields The custom fields element.
Roots .sf-el-product The product element.
Roots .sf-el-buy-button The buy button element.
Roots .sf-el-cart-button The cart button element.
Roots .sf-el-pay-button The pay button element.
Roots .sf-el-tip The tip element.
Roots .sf-el-offers The add-ons and upsells element.
Layout .sf-el-section A titled block inside an element (contact, details, payment).
Layout .sf-el-heading Section and element headings.
Layout .sf-el-subheading Small text next to or under a heading.
Layout .sf-el-divider A hairline between blocks.
Layout .sf-el-grid Two or three fields side by side.
Fields .sf-el-field A label, its input and its hint or error.
Fields .sf-el-field-label A field label.
Fields .sf-el-field-input Text inputs and text areas.
Fields .sf-el-field-select Selects (country, custom field choices).
Fields .sf-el-field-hint Help text under a field.
Fields .sf-el-field-error The error under a field.
Fields .sf-el-field-optional The “Optional” mark next to a label.
Fields .sf-el-check A checkbox row (marketing, custom checkboxes).
Fields .sf-el-check-box The checkbox itself.
Fields .sf-el-check-label The text next to a checkbox.
Fields .sf-el-code-row The code input and its Apply button.
Fields .sf-el-verify The email confirmation code box.
Buttons .sf-el-button Every button.
Buttons .sf-el-button-primary Primary buttons (Add to cart, Apply).
Buttons .sf-el-button-secondary Secondary buttons.
Buttons .sf-el-button-ghost Quiet text buttons (Remove, Have a code?).
Buttons .sf-el-pay Yes The pay button, wherever it is.
Buttons .sf-el-link Text links.
Buttons .sf-el-chip Small toggles (tip amounts, test outcomes).
Cart .sf-el-lines The list of cart lines.
Cart .sf-el-line Yes One cart line.
Cart .sf-el-line-image A line’s cover or picture.
Cart .sf-el-line-name Yes A line’s product name.
Cart .sf-el-line-option Yes A line’s option (“30 Days”, “Commercial license”).
Cart .sf-el-line-qty Yes The quantity stepper or badge.
Cart .sf-el-line-price Yes A line’s price.
Cart .sf-el-line-notice A line’s notice (“Only 3 left”).
Cart .sf-el-qty-button The + and − buttons.
Cart .sf-el-empty The empty cart message.
Totals .sf-el-totals Yes The totals box.
Totals .sf-el-row Yes One row of the totals (Subtotal, Tax).
Totals .sf-el-row-label Yes A totals row label.
Totals .sf-el-row-amount Yes A totals row amount.
Totals .sf-el-discount Yes Discount and gift card rows.
Totals .sf-el-total Yes The total row.
Totals .sf-el-total-label Yes The word “Total”.
Totals .sf-el-total-amount Yes The amount the buyer pays.
Totals .sf-el-applied-code An applied discount or gift card chip.
Payment .sf-el-method-list The list of ways to pay.
Payment .sf-el-method One way to pay.
Payment .sf-el-method-icon A method’s icon.
Payment .sf-el-method-label A method’s name.
Payment .sf-el-method-note Yes What happens next (“You finish on PayPal”) or a price change.
Payment .sf-el-method-panel What opens under the selected method.
Payment .sf-el-test-card The test card shown in test mode.
Payment .sf-el-test-badge Yes The “Test mode” badge.
Payment .sf-el-outcome A simulated outcome chip in test mode.
Payment .sf-el-limit-note Why a way to pay is not offered for this total.
Offers .sf-el-offer One add-on or upsell.
Offers .sf-el-offer-title An offer’s title.
Offers .sf-el-offer-price Yes An offer’s price.
Offers .sf-el-progress The reward progress bar (“$5 to free bonus”).
Offers .sf-el-progress-bar The filled part of the progress bar.
Product .sf-el-product-cover The product picture.
Product .sf-el-product-title The product name.
Product .sf-el-product-description The short description.
Product .sf-el-product-price Yes The product price.
Product .sf-el-product-compare The crossed-out regular price.
Product .sf-el-variant One option in the option picker.
Product .sf-el-tier One quantity price (“5+ · 10% off”).
Product .sf-el-badge Small labels (Sale, Instant delivery).
Product .sf-el-count The item count on the cart button.
Legal .sf-el-consent-terms Yes The terms checkbox and its text.
Legal .sf-el-waiver Yes The EU and UK immediate delivery box.
Legal .sf-el-seller Yes Who sells (“Sold by …”).
Legal .sf-el-trust The secure checkout note and badges.
Feedback .sf-el-alert A message box.
Feedback .sf-el-alert-title A message box title.
Feedback .sf-el-alert-text A message box text.
Feedback .sf-el-spinner The loading spinner.
Feedback .sf-el-skeleton Placeholder bars while an element loads.
Feedback .sf-el-done The paid state.
Overlay .sf-el-overlay The backdrop of the overlay checkout (buy and cart buttons).
Overlay .sf-el-overlay-panel The overlay’s panel.
Overlay .sf-el-overlay-close The overlay’s close button.

Starting from nothing

preset: "bare" strips the elements down to structure: no borders, no backgrounds, no radius, no padding. Layout, type and focus rings stay, so it is still usable while you build your own look with rules.

brand: { preset: "bare", rules: { /* all yours */ } }

Changing the brand later

elements.update({ brand: { mode: "dark", tokens: { accent: "#0f766e" } } });

Restyles every element in place: no remount, no flash, and the buyer keeps what they typed.

Was this page helpful?