Skip to content

Powered by Grav

Coming soon — KahunaCart is in final testing. Join the list and be first to know. Join the list

Theming

This page is for Twig and CSS themers. It covers the --kahunacart-* custom properties, the classes the shipped stylesheet targets, and how to choose between a variable, your own CSS, and a template override.

For the routes, templates and Twig functions behind the markup, see Storefront.

What ships

KahunaCart enqueues two files, on its own storefront routes and nowhere else:

File What it does
assets/kahunacart.css One plain CSS file. No Tailwind, no preprocessor, no external fonts, no images.
assets/kahunacart-options.js One small script for the product-option picker. No framework, no build step, no dependencies, no network calls.

Both are cache-busted by file mtime, so a plugin update takes effect without a hard refresh. Webhook and download responses exit before a page exists and never receive either file.

Every rule is scoped to .kahunacart, the wrapper class the shipped templates carry, so the stylesheet cannot repaint the rest of your site. Component rules wrap their outer selector in :where(), which contributes nothing to specificity, so a plain class selector in your own CSS beats most of what the plugin sets. Nothing in the file uses !important.

The stylesheet also handles :focus-visible rings on every control, tabular figures on money columns, prefers-reduced-motion, and cart tables that scroll inside themselves on a phone.

Note

The storefront is server-rendered HTML and works with scripting off, the option picker included. See The buy button and the option picker.

How token resolution works

The stylesheet uses internal --kc-* tokens. You never set those. Each token takes the first value that is defined, down this chain:

TXT
--kahunacart-*   your override, site or theme
--grav-*         the theme's own design tokens, if it publishes them
--theme-accent   a generic accent hook, for the two accent tokens only
neutral fallback derived from --kc-fg, or an absolute colour

If your theme already defines --grav-fg, --grav-border, --grav-bg-elevated and --grav-radius-md, the storefront picks them up and you write no CSS at all.

Borders, muted text and faint fills are all derived from --kahunacart-fg with color-mix(): borders at 13% and 24%, muted text at 68%, subtle text at 60%, faint fills at 3.5%. Retarget --kahunacart-fg and the whole neutral layer moves with it.

Tokens are declared on both :root and .kahunacart, so either selector works. Use .kahunacart to restyle the storefront without touching anything else on the site.

Custom property reference

Every hook, what it paints, and what it resolves to when you leave it alone.

Property Paints Falls back to
--kahunacart-fg Text, and the anchor the neutral ramp is derived from --grav-fgCanvasText
--kahunacart-bg The page ground; used where a control must be opaque, such as a <select> option list --grav-bgCanvas
--kahunacart-border Card, table, fieldset and chip borders --grav-border → 13% of fg
--kahunacart-border-strong Input borders, the rule above the order total, button outlines --grav-border-strong → 24% of fg
--kahunacart-muted Secondary text: product summaries, field labels, totals labels --grav-fg-muted → 68% of fg
--kahunacart-subtle Tertiary text: table column heads, the cart remove control at rest --grav-fg-subtle → 60% of fg
--kahunacart-accent Ink and borders: hover states, the outline of a chosen shipping or payment row --grav-purple-bright--theme-accentlight-dark(#0b57d0, #7cacf8)
--kahunacart-accent-strong The only token that fills a box behind a label: active category chip, Checkout and Place order buttons --grav-purple--theme-accentlight-dark(#0b57d0, #2563eb)
--kahunacart-accent-ink Prices and product-title hover, where the accent is text on the page ground --grav-purple--kahunacart-accent
--kahunacart-on-accent-strong The label colour on an accent-filled box Computed from the fill's luma, black or white; #fff on older browsers
--kahunacart-surface Faint panels: flash messages, download rows, the order meta block --grav-bg-elevated → 3.5% of fg
--kahunacart-surface-2 The deeper panel, used for the add-to-cart block --grav-bg-subtle--kahunacart-surface
--kahunacart-card Product card fill; transparent by default so a patterned page background is not covered in near-invisible grey rectangles --grav-bg-elevatedtransparent
--kahunacart-card-ratio The aspect ratio of a product card's image 4 / 3
--kahunacart-input Form control fill --grav-bg-subtletransparent
--kahunacart-swatch The fill of a colour swatch that has no colour stored on it 12% of fg
--kahunacart-radius Buttons, inputs, small panels --grav-radius-md8px
--kahunacart-radius-lg Cards, fieldsets, the buy block --grav-radius-lg12px
--kahunacart-font-mono Coupon codes and category counts --grav-font-monoui-monospace, SFMono-Regular, Menlo, monospace
--kahunacart-ease Every transition's easing --grav-easeease
--kahunacart-duration Every transition's duration --grav-duration-2160ms
--kahunacart-success Applied coupon, discount lines on the receipt --grav-success#1d9d61
--kahunacart-warning Offline payment instructions on the confirmation page --grav-warning#b8860b
--kahunacart-danger Error messages, hovering the cart's remove control, the download denial notice --grav-danger#c0392b

Override the storefront colours

Six properties usually cover the whole job.

  1. Open your theme's CSS file.

  2. Add the properties you want to change:

    CSS
    :root {
       --kahunacart-accent: #2f7d6f;        /* hover ink, chosen-option outlines */
       --kahunacart-accent-strong: #1d5e53; /* filled: Checkout, Place order, active chip */
       --kahunacart-accent-ink: #1d5e53;    /* prices */
       --kahunacart-radius: 4px;
       --kahunacart-radius-lg: 6px;
       --kahunacart-card: #fff;             /* product cards get a real fill */
    }
    
  3. Reload a storefront page.

The storefront now renders in your colours and your corner geometry. Everything you did not name still follows the page.

Support your theme's dark mode

Redefine only what changes.

  1. Add a block under your theme's dark-mode selector:

    CSS
    html[data-theme="dark"] {
       --kahunacart-fg: #e6e8ee;
       --kahunacart-bg: #14161c;
       --kahunacart-card: #1b1e26;
       --kahunacart-accent: #7cc9b8;
    }
    
  2. Reload a storefront page in dark mode.

The neutral ramp follows the new --kahunacart-fg. You do not have to restate borders, muted text or faint fills.

Tip

If your theme flips dark mode without setting color-scheme, the stylesheet detects the common conventions (html[data-theme="dark"], html[data-bs-theme="dark"], html.dark, body[data-theme="dark"], body.dark) and sets color-scheme on the storefront. This only matters if you set no tokens of your own.

Turn off the built-in stylesheet

  1. Set builtin_css to false, or use the matching toggle on the General tab of the plugin's admin settings.
  2. Reload a storefront page.

No stylesheet is enqueued and the kahunacart-* classes are yours to style from scratch. The switch does not turn off the option picker's script, which a theme still needs to resolve a variant.

Important

Turn the stylesheet off only when your theme wants total control of storefront styling, or when you have replaced every shipped template and the classes are no longer rendered. Otherwise leave it on and override what you need, so you keep the focus states, tabular figures, reduced-motion handling and small-screen table behaviour.

The buy button and the option picker

partials/kahunacart-buy-button.html.twig is the partial themes include most. It always renders both of these:

  • The option widgets, when the product defines axes. Each axis gets a dropdown, a row of button pills, or colour swatches, plus a <script type="application/json"> carrying the picker's data. They ship hidden.
  • The plain <select name="variant_id">, which is the field the form posts.

assets/kahunacart-options.js reveals the widgets, visually hides the select, writes the resolved variant into it, and fires a change event on it. Three consequences follow:

  • The form works with JavaScript off. A browser that never loads the script posts exactly what it always did.
  • The POST contract is unchanged: variant_id and qty.
  • A theme listening for change on the select keeps working, whichever control the customer used.

The template variables are product, variants, options and options_payload. kahunacart_product('slug') sets all four.

Important

If you build the variable yourself, pass options (the presented axes) and options_payload (the encoded JSON). Without them the widgets do not render and the plain select stands.

Colour swatches read their colour from an inline --kc-swatch custom property, set on .kahunacart-option-choice and painted by .kahunacart-option-dot. Restyling a swatch is a normal CSS rule.

Render the buy button outside the storefront routes

A quick-view modal or a product card with an inline buy form sits on a route where the script has not been enqueued for you.

  1. Add the script in your template:

    TWIG
    {% do assets.addJs('plugin://kahunacart/assets/kahunacart-options.js', {loading: 'defer'}) %}
    
  2. If you inject the buy form after page load, call window.kahunacartOptions.start(container).

The picker is now active on that page.

Style the catalog sidebar, tree and tags

The catalog sidebar resolves through the same token chain as the rest of the storefront, so a theme that has set --kahunacart-accent and --kahunacart-fg gets the tree, the facet panel, the breadcrumb and the subcategory cards in its own colours.

The two-column layout only exists when there is a sidebar. The wrapper is not emitted on a store with no categories and no tags, so on a flat catalog these rules never apply. See Storefront. Below 40rem the layout collapses to one column with the sidebar above the listing.

The tree is <details> and <summary> with the browser's own disclosure marker, and the summary is display: list-item. ::marker is coloured from --kc-subtle. The indent is padding on the nested list rather than a margin on the item, so hover and current-page backgrounds run the full width of the rail at every depth.

Tags are not styled as category chips: a category chip goes to a place in the catalog, a tag goes to a filtered listing. If you restyle one, keep the two distinguishable.

Choose between a variable, extra CSS, and a template override

Three levels, cheapest first.

Level Use it when Update cost
Set a custom property You are changing colour, radius, typeface or motion None
Write your own CSS rules You want different spacing, a different card layout, or a component that looks structurally unlike what ships None
Override a template The markup is wrong for you: a missing product image, an extra checkout field, a different element order for screen readers, or your own class names The copy is frozen at the version you took it from

Plugin rules sit at low specificity, so a plain .kahunacart-product-card { … } in your theme wins. If you need !important, report it as a bug.

To override a template, copy the file from the plugin's templates/ into user/themes/<your-theme>/templates/ at the same relative path and edit it. The plugin appends its own directory to Twig's search path, so your theme wins. See Storefront for the full template list.

Warning

An overridden template does not receive later KahunaCart changes to that file, including added fields and accessibility fixes. Override markup when you need markup, not when you need a colour.

Class reference

What the stylesheet targets, grouped by page. Every class here is rendered by the shipped templates.

Area Classes
Wrapper kahunacart on every page, plus one of kahunacart-products, kahunacart-category, kahunacart-product, kahunacart-cart, kahunacart-checkout, kahunacart-order, kahunacart-processing, kahunacart-denied, kahunacart-account, kahunacart-search-results, kahunacart-invoice
Listing kahunacart-product-grid, kahunacart-product-card, kahunacart-card-link, kahunacart-card-media, kahunacart-price, kahunacart-listing-more
Categories kahunacart-categories, kahunacart-category-chip (.is-active), kahunacart-category-count, kahunacart-category-description, kahunacart-category-media
Catalog layout kahunacart-catalog-layout, kahunacart-catalog-main, plus kahunacart-has-nav on the page wrapper when there is a sidebar
Category tree kahunacart-catalog-nav, kahunacart-nav-title, kahunacart-cat-tree, kahunacart-cat-list, kahunacart-cat-item, kahunacart-cat-branch, kahunacart-cat-summary, kahunacart-cat-link, kahunacart-cat-count, plus the state classes is-current and is-ancestor
Filter panel kahunacart-filters, kahunacart-filter-group, kahunacart-filter-options, kahunacart-filter-option, kahunacart-filter-count, kahunacart-filter-price, kahunacart-filter-range, kahunacart-filter-field, kahunacart-filter-dash, kahunacart-filter-actions, kahunacart-filter-clear
Search kahunacart-search, kahunacart-search-field, kahunacart-search-summary, kahunacart-search-more
Breadcrumb kahunacart-breadcrumbs, kahunacart-breadcrumb-list, kahunacart-breadcrumb-item
Subcategory cards kahunacart-subcategory-grid, kahunacart-subcategory-card (.has-image), kahunacart-subcategory-link, kahunacart-subcategory-media, kahunacart-subcategory-title, kahunacart-subcategory-count
Tags kahunacart-tags, kahunacart-tags-title, kahunacart-tag-list, kahunacart-tag
Product kahunacart-summary, kahunacart-description, kahunacart-buy, kahunacart-qty, kahunacart-compare-price
Product gallery kahunacart-gallery, kahunacart-gallery-main, kahunacart-gallery-slide, kahunacart-gallery-thumbs
Option picker kahunacart-options, kahunacart-option, kahunacart-option-name, kahunacart-option-select, kahunacart-option-choices, kahunacart-option-choice with kahunacart-option-pill or kahunacart-option-swatch, kahunacart-option-dot, kahunacart-option-label, kahunacart-option-status, kahunacart-variant-picker, plus the state classes is-selected, is-unavailable and is-sold-out
Cart kahunacart-cart-table, kahunacart-remove, kahunacart-checkout-link, kahunacart-cart-account
Coupon kahunacart-coupon, kahunacart-coupon-code, kahunacart-coupon-remove, kahunacart-coupon-active
Totals kahunacart-totals, kahunacart-adjustment with --discount, --shipping or --tax, kahunacart-total
Checkout kahunacart-checkout-form, kahunacart-checkbox, kahunacart-provider, kahunacart-shipping-methods, kahunacart-shipping-method, kahunacart-shipping-hint, kahunacart-shipping-none, kahunacart-place-order
Order kahunacart-order-meta, kahunacart-order-items, kahunacart-order-links, kahunacart-offline-instructions
Order status badges kahunacart-status with kahunacart-status--payment, kahunacart-status--order or kahunacart-status--fulfillment, plus kahunacart-status--{value} for the status value itself
Downloads kahunacart-downloads, kahunacart-download-list, kahunacart-download, kahunacart-download-product, kahunacart-download-meta
Customer account kahunacart-account-nav, kahunacart-account-nav-item (--current, --logout), kahunacart-account-panel, kahunacart-account-identity, kahunacart-account-orders, kahunacart-account-order-actions, kahunacart-account-order-history, kahunacart-account-addresses, kahunacart-account-downloads, kahunacart-account-empty
Addresses kahunacart-address, kahunacart-address-form, kahunacart-address-grid, kahunacart-address-list, kahunacart-address-card (--default), kahunacart-address-flags, kahunacart-address-flag, kahunacart-address-actions, kahunacart-address-edit, kahunacart-shipping-address
Address check kahunacart-address-check, kahunacart-address-check-title, kahunacart-address-check-lead, kahunacart-address-check-warning, kahunacart-address-check-options, kahunacart-address-check-option, kahunacart-address-check-label, kahunacart-address-check-suggested, kahunacart-address-check-accept, kahunacart-address-check-keep
Invoice kahunacart-invoice-toolbar, kahunacart-invoice-header, kahunacart-invoice-store, kahunacart-invoice-parties, kahunacart-invoice-party, kahunacart-invoice-meta, kahunacart-invoice-items, kahunacart-invoice-tax-id, kahunacart-invoice-email, kahunacart-invoice-footer
Denial kahunacart-denied-message, plus kahunacart-denied--{reason} where reason is not_found, revoked, expired, exhausted, order_refunded or unreadable
Messages kahunacart-messages, kahunacart-message with --error, --warning, --info or --success
Utility kahunacart-hint, kahunacart-link-button (--danger), kahunacart-visually-hidden, kahunacart-no-print

The --{modifier} variants on adjustments, denials, statuses and messages are all rendered whether or not the shipped stylesheet paints them, so you can branch on any of them from your own CSS. The stylesheet leaves most denial reasons looking identical.