Customizing & Styling

Croissant's surfaces are built to inherit your theme rather than impose a look. This page is the complete reference: every setting, the CSS escape hatch for what the settings do not cover, and how to get a new setting added when CSS is the wrong answer.

The Four Layers

Work down this list in order. Each layer is more work and less durable than the one above it, so stop as soon as one gets you what you need.

LayerWhereUse it for
1. Block settingsTheme editor / checkout editorPlacement, alignment, spacing, sizes, copy variant. Survives app updates
2. Style mirroringButton style source / Link style sourceFonts, colors, radii, hover states — copied from a button you already have. Almost always the right answer
3. Custom CSSYour theme's stylesheetTheme blocks only. The escape hatch for anything with no setting — unsupported, and yours to re-verify after updates
4. Ask Croissantsupport@croissant.comCheckout block styling, copy changes, and anything you had to reach for CSS to do — see requesting a change

Theme Block Settings

Set these under Online Store → Themes → Customize → App embeds. Placement settings are per surface — the cart drawer and the cart page are configured independently. Everything else is shared across both surfaces, which is the single most important thing to know about these blocks: the button looks identical on both surfaces and sits differently. If you need the two to look different, that is a CSS job.

Pay with Croissant Card

SettingValuesDefault
Cart injection target (drawer)CSS selectorbutton[name='checkout']
Cart page injection targetCSS selectorempty — cart page off
Injection positionbefore, afterbefore
Alignmentleft, center, rightcenter
Text positionabove, belowabove
Button widthfull, autofull
Margin / padding top and bottom−48px to 48px, per surface0
Cart drawer close buttonCSS selectorempty — falls back to common drawer patterns
Hide info iconBooleanOff
Hide logoBooleanOff
Logo size12px to 48px22px
Button style sourceCSS selector to mirror classes frombutton[name='checkout']
Button custom classesSpace-separated classesempty
Text font size / line height / letter spacingFixed px steps, or inheritinherit
Button height / padding X / padding YFixed px steps, or inheritinherit
Button font size / line height / letter spacingFixed px steps, or inheritinherit
Button/text gap0px to 48px0
Label vertical offset−8px to 8px0
Tip
Button style source beats every override below it. It copies the classes off the element you point at, so the Croissant CTA picks up your theme's font, radius, colors, and hover state for free. Reach for the height/padding/font-size selects only when mirroring leaves something visibly wrong — and reset them to inherit before you debug anything else. Custom classes are the fallback for when there is no button on screen to mirror.

The Link block is the lighter inline variant. It has the same per-surface placement settings, with these differences:

SettingValuesDefault
Copy variantshort, medium, long, minimal, savemedium
Custom link textFree text, or empty to use the selected copy variantempty
Injection positionbefore, afterafter — note this differs from the Card block
Margin top−48px to 48px8px — again unlike the Card block
Internal gap0px to 32px, or inheritinherit
Link style source / custom classesCSS selector / space-separated classesempty
Link font size / line height / letter spacingFixed px steps, or inheritinherit

It has no text position, button width, logo, height, padding, button/text gap, label offset, or hide-info-icon setting. The copy variant or custom link text is what you tune instead, and the info icon has to be hidden with CSS if you want it gone.

A Worked Example

A typical Dawn-derived theme, running the Card block in the drawer and on the cart page, mirroring the theme's own checkout button:

text
Flyout placement
  Cart injection target        #CartDrawer button[name='checkout']
  Injection position           before
  Alignment                    center
  Button width                 full
  Margin bottom                -8

Cart page placement
  Cart page injection target   .cart__ctas
  Injection position           before
  Alignment                    center

Behavior
  Cart drawer close button     .drawer__close

Button
  Button style source          button[name='checkout']
  Button custom classes        (empty — mirroring covers it)

Before saving a selector, confirm it resolves to exactly one element on the surface you are configuring. Run this in the console with the drawer open, then again on /cart:

js
// Should log exactly 1
document.querySelectorAll("#CartDrawer button[name='checkout']").length;

// Dump the settings the block actually received, as resolved by the theme
console.table(
  document.getElementById("croissant-pay-card-template").dataset
);
Info
That second snippet is the fastest way to prove a settings change actually shipped. The block reads every setting off data-* attributes on its template element, so what dataset shows is exactly what the block is working from. An empty value means that setting is unset — and an empty injection target means that surface is switched off.

Checkout Block Settings

Set these under Settings → Checkout → Customize on the Croissant app block. The editor shows every field empty rather than pre-filled; empty means the default. In effect:

json
{
  "display_variant": "default",
  "button_kind": "primary",
  "button_text": "Redeem Croissant Credit",
  "supporting_link_text": "",
  "logo_style": "filled_dark",
  "show_logo": true,
  "info_modal_variant": "steps",
  "hide_offer": false,
  "show_border": true
}

Full option lists are on the Checkout Block page. Two behaviors are worth calling out because they are not obvious from the field names:

  • hide_offer hides the default offer and its info icon. Set supporting_link_text when you want concise supporting copy to remain as a route to the explainer modal.
  • simple_button is button-only by default. It ignores show_border and renders no offer copy. Setting supporting_link_text adds a link and info icon beneath the button while keeping the compact layout.

Compact Copy-Forward Example

Use this when the design calls for a text-only redemption button with a short value-proposition link beneath it:

text
Display Variant       simple_button
Button Type           primary
Button Text           Redeem Croissant Credit
Supporting Link Text  Shop, Earn, Resell — with Croissant
Show Button Logo      false
Hide Offer            true
Show Border           false

The supporting link opens the same Croissant explainer modal as the standard offer's info icon. Change the copy freely per brand; leaving the field empty restores the previous button-only behavior.

Styling the Checkout Button

There is no CSS escape hatch in checkout, and this is Shopify's design rather than a gap in ours: checkout UI extensions render in a sandboxed frame using Shopify's own components, and no app — Croissant included — can inject a stylesheet into it. What you get in exchange is that the Croissant button is a native Shopify checkout button, so your checkout branding already applies to it.

So the levers are:

  • button_kind — maps straight onto Shopify's primary, secondary, and plain button styles. This is how you make Croissant subordinate to your main Pay now button.
  • Settings → Checkout → Customize → Style — your brand colors, typography, and corner radius flow into the Croissant button because it is one of Shopify's buttons.
  • show_border and display_variant — the only structural controls: card-with-border, or bare button.

On Plus you can also drive checkout branding from the Admin API, which reaches the Croissant button for the same reason:

graphql
# Shopify's API, not Croissant's — this restyles every native checkout
# button, including the Croissant one. See Shopify's checkout branding
# docs for the full field and enum list.
mutation RestyleCheckoutButtons($profileId: ID!) {
  checkoutBrandingUpsert(
    checkoutProfileId: $profileId
    checkoutBrandingInput: {
      customizations: { primaryButton: { cornerRadius: BASE } }
    }
  ) {
    checkoutBranding { customizations { primaryButton { cornerRadius } } }
    userErrors { field message }
  }
}
Warning
Anything beyond those levers — different copy, a different layout, the logo somewhere else — has to be built into the extension by Croissant. There is no workaround, so raise it as a request rather than spending time looking for one.

Custom CSS: The Escape Hatch

The theme blocks render into your own DOM, so your stylesheet can reach them. This is the supported way to handle the last 5% — a heading you want gone, per-surface differences, a logo gap the settings do not expose.

Warning
Class names are implementation details, not a versioned API. Croissant does not guarantee them across app updates. Keep overrides small, comment them with why they exist, re-check them after every Croissant app update or theme change, and tell us what you wrote — a CSS override is a bug report about a missing setting.

Where to Put It

In order of preference: your theme's Custom CSS field (Theme editor → Theme settings → Custom CSS), then your theme's own stylesheet, then a snippet you include from layout/theme.liquid. Do not edit the Croissant extension's stylesheets — they are app assets and every update overwrites them.

Load order between your theme's CSS and an app embed's CSS is not guaranteed, and the blocks set some values as inline styles. Assume you need !important and one of the class hooks below.

The Rendered DOM

This is what the Card block injects next to your target. Target the injected .croissant-pay-card, never the <template> it is cloned from — styles never apply inside a template.

html
<div class="croissant-pay-card" data-croissant-surface="flyout">
  <span class="croissant-pay-card__heading">Use Croissant at Checkout.</span>

  <span class="croissant-pay-card__incentive">
    <span class="croissant-pay-card__incentive-text">Get $50 off your order of $250+</span>
    <button class="croissant-pay-card__info-btn"><svg>…</svg></button>
  </span>

  <button class="croissant-pay-card__cta">
    <div class="croissant-pay-card__cta-inner">
      <svg>…</svg>
      <span class="croissant-pay-card__cta-full">Continue with Croissant</span>
      <span class="croissant-pay-card__cta-short">Pay with Croissant</span>
    </div>
  </button>
</div>

On non-Plus plans, an authorized session replaces the CTA subtree entirely:

html
<div class="croissant-pay-card" data-croissant-surface="cart">
  <div class="croissant-pay-card__status">
    <span class="croissant-pay-card__status-title">Croissant Payment Authorized</span>
    <span class="croissant-pay-card__status-sub">You're earning $12 back</span>
    <!-- present only in the "cart changed" state -->
    <button class="croissant-pay-card__reauthorize">Reauthorize with Croissant</button>
    <button class="croissant-pay-card__void">Remove Croissant</button>
  </div>
</div>

The Link block and the modal follow the same naming:

HookWhat it is
.croissant-pay-linkLink block root
.croissant-pay-link__textOffer copy. Absent on the minimal and save variants
.croissant-pay-link__cta-lineInline row holding the link and info icon
.croissant-pay-link__ctaThe link itself
.croissant-pay-link__prefixThe words when you, on the long variant only
.croissant-pay-link__info-btnInfo icon. No setting hides this one
.croissant-pay-link__status / __status-title / __status-subAuthorized and cart-changed states
.croissant-pay-link__reauthorize / __voidReauthorize and remove controls
#croissant-info-modalModal root. Gets .is-open while open
.croissant-modal__overlay / __card / __close / __headerModal chrome
.croissant-modal__step / __step-circle / __step-title / __step-bodyThe three steps in the steps variant

What CSS Can and Cannot Reach

The blocks apply your settings as inline styles. Most are normal declarations, so an !important rule in your stylesheet wins. A few are set as inline !important, which no stylesheet can beat — those are settings-only, permanently.

PropertyOnOverride with
margin, padding, text-alignCard / Link rootCSS with !important
height, padding, font-size, line-height, letter-spacingCard CTACSS with !important
font-size, line-height, letter-spacingCard heading and offer textCSS with !important
Everything not listed belowAnywhereCSS — plain specificity is usually enough
width, max-width, align-selfCard CTASettings only. Set inline as !important — use Button width and Alignment
font-size, line-height, letter-spacingLink CTASet the matching Link setting back to inherit to hand control back to CSS — while it holds a px value it is inline !important
Warning
The blocks also set display: none inline when the integration is not live or the currency is not USD. Never override that. The button would render into a flow that cannot complete — a shopper who clicks it reaches a session your store cannot honor.

Worked Overrides

Each of these covers something the settings genuinely do not expose. They are the cases we see most often.

css
/* The heading copy is fixed. Hide it and let the offer line carry the message. */
.croissant-pay-card__heading {
  display: none !important;
}

/* Style the two surfaces differently — styling settings are shared, so this is
   the only way to do it. */
.croissant-pay-card[data-croissant-surface="cart"] .croissant-pay-card__cta {
  text-transform: uppercase !important;
  letter-spacing: 0.08em !important;
}

/* Gap between the logo and the button label. Fixed at 8px, no setting for it. */
.croissant-pay-card__cta-inner {
  gap: 4px !important;
}

/* The Link block has no "hide info icon" setting. */
.croissant-pay-link__info-btn {
  display: none !important;
}

/* Recolor the info icon. The block locks its stroke against theme bleed, so you
   have to match that specificity. */
.croissant-pay-card__info-btn svg circle,
.croissant-pay-card__info-btn svg path {
  stroke: currentColor !important;
}

/* The Croissant logo inherits the button's text color, so recoloring the label
   recolors the mark with it. */
.croissant-pay-card__cta {
  color: #171717 !important;
}

/* The label swaps to a shorter string under 480px. Force one everywhere. */
@media (max-width: 480px) {
  .croissant-pay-card__cta-full { display: inline !important; }
  .croissant-pay-card__cta-short { display: none !important; }
}

/* Make the authorized state match your theme instead of the default underline. */
.croissant-pay-card__void {
  opacity: 1 !important;
  text-decoration: none !important;
  border-bottom: 1px solid currentColor !important;
}

Two Things Not to Do

  • Do not restyle with JavaScript. The blocks watch the DOM and re-inject themselves whenever your theme tears the cart down — a drawer close and reopen, a quantity change. Every re-injection is a fresh clone, so your mutations are gone and your listeners are on detached nodes. CSS survives this; JS does not.
  • Do not reposition with CSS. Placement is what the injection target and position settings are for, and they run before paint. Absolutely-positioning the card into place instead means it moves the next time your theme's cart markup changes, with no console warning to tell you.

Opening the Modal Yourself

The info modal listens on document for a croissant:open-modal event, so any trigger you own can open it. This is the one supported programmatic hook — useful when you want the explainer behind your own How does this work? link rather than the built-in icon.

js
document.querySelector("#my-croissant-explainer-link")
  .addEventListener("click", (event) => {
    document.dispatchEvent(
      new CustomEvent("croissant:open-modal", {
        detail: {
          // Croissant closes the cart drawer first so the modal is not
          // trapped behind it. Pass your trigger so it can find the drawer,
          // or a selector for the drawer's close button.
          trigger: event.currentTarget,
          drawerCloseButton: ".drawer__close",
        },
      })
    );
  });

Both detail fields are optional. With neither, Croissant falls back to walking up from the trigger and matching common drawer patterns — cart-drawer, #CartDrawer, .mini-cart, an open <details> or <dialog> — which is also what happens when you leave the Cart drawer close button setting empty.

Info
The Croissant Info Modal app embed has to be enabled for this to do anything, and its content comes from your Croissant configuration rather than the theme editor. The only theme-side setting is the steps / covers layout choice.

Requesting a New Customization

Every CSS override you write is a setting we should probably ship. Send them to support@croissant.com or your integration contact — including the ones you already worked around, so the next store does not have to.

Include this much and we can usually act on it without a call:

text
Store domain:     yourstore.myshopify.com
Surface:          cart drawer / cart page / checkout
Block:            Pay with Croissant Card / Link / Info Modal / checkout block
What you want:    e.g. control the gap between the logo and the label
Why settings
don't cover it:   e.g. cta-inner gap is fixed at 8px; no setting exposes it
Current stopgap:  the CSS you are shipping today, if any
Screenshot:       before / desired
Kind of requestWhat happens
Placement, spacing, sizing, alignment, or a new copy variantStrong candidate for a new theme-editor setting — these are per-store by nature
Anything at all in the checkout blockHas to be built by Croissant. There is no CSS path, so these are always requests
One-off cosmetic fit against an unusual themeStays CSS on your side. Still tell us — a pattern across stores becomes a setting
Croissant logo and wordmark, the modal's explainer copy, the 10% figure, discount titles on the order summaryNot configurable, and not something CSS should be used to defeat. Talk to us about what the shopper needs to see instead

Next: Discounts & Cart Data → how authorization becomes discount lines