Shopify Builds>Three Ships>Themed host section for Shopify Forms app blocks

Themed Host Section for Shopify Forms App Blocks

A section whose only block type is @app, wrapping any app block in the theme's width, padding and background scaffolding — and, when a merchandiser switches it on, writing the --forms-heading-* and --forms-body-* custom properties that Shopify's Forms embed reads for its own typography.

An app block renders where the section's block list puts it, with the app's defaults. Three Ships' campaign pages needed to mount Shopify's first-party Forms embed with the theme's spacing and the brand's type. We built a section whose only block type is @app, wrapping any app block in the theme's width and padding scaffolding and, when a merchandiser switches it on, writing the --forms-heading-* and --forms-body-* custom properties the embed reads for its typography.

The Problem

An app block is a slot. The theme decides where the slot sits; the app decides everything inside it. Shopify Forms renders its sign-up form as a web component, <shopify-forms-embed>, whose markup lives in a shadow root, so the theme's stylesheet does not select into it — a .page-width h2 { … } rule stops at the boundary. What does cross that boundary is inheritance: a CSS custom property set on the element is readable inside the shadow tree, and the Forms embed reads a documented set of them for its heading and body type.

Three Ships' marketing team runs dozens of one-off campaign pages — giveaways, photo submissions, contests, newsletter sign-ups — and most of them need a sign-up form. The form itself is Shopify's and should stay Shopify's. What the pages needed from the theme was a place to put it that behaves like every other section: the theme's page width, the theme's vertical rhythm, a background the merchandiser can set, and the brand's typefaces on the heading and body copy without a developer per page.

The Constraint

The theme is Palo Alto 5.8.0 by Presidio Creative, a paid premium theme. Its sections carry their own width and padding conventions — the wrapper classes and the --PT / --PB padding properties — and a host for app blocks had to honour those rather than invent new ones, so a form mounted on a campaign page lines up with the hero above it and the product slider below it.

The embed's typography is set through the custom properties the app reads, which fixed the shape of the bridge: the section writes values into those properties. And every value had to come from the theme editor, because the point of the section is that a merchandiser places and styles a form without a ticket.

The brand's typefaces are licensed faces, self-hosted in the theme. A free-text font field would have let a page name a face the theme does not carry, so the font choice had to be a closed list. And the typography controls are irrelevant to most app blocks the section will ever host, which argued for keeping them out of sight unless a merchandiser asks for them.

What We Built

apps.liquid, a section named "Apps" with one block type, {"type": "@app"}, and a disabled_on rule keeping it out of the header, aside and footer groups.

The markup is deliberately small. A wrapping div with the theme's section-padding class and an id of App--{{ section.id }}, an inner div carrying whichever wrapper class the merchandiser picked — full width padded, page width, or page width narrow — and a loop that does {% render block %} for each app block. A {% style %} block scoped to the section id sets --PT and --PB from two padding ranges, and emits a background rule only when bg_color is neither blank nor fully transparent, so an unset colour leaves the page's own ground showing through.

The typography bridge sits behind one checkbox, forms_override_enabled. When it is on, the same {% style %} block emits a rule on the shopify-forms-embed element itself: --forms-heading-font-style, --forms-heading-font-weight, --forms-heading-font-size and --forms-heading-font-family, composed into a --forms-heading-font shorthand from those four, plus --forms-heading-line-height; and the same set for the body — --forms-body-font-style, -weight, -size, -family and the composed --forms-body-font. Every value is a section setting: a font-family select, a size range, a weight select from 300 to 900, a style select of normal or italic, and for the heading a line-height range. The properties land on the host element and inherit into the shadow tree.

The controls are a closed system. The font-family select offers the brand's licensed faces — Rauschen-ABook, Rauschen-ABold-Desktop, span-compressed, ABCFavoritMono-Bold — plus Helvetica, each with its fallback stack written into the option value, so a merchandiser cannot type a face the theme does not ship. And every typography setting carries Shopify's visible_if condition bound to the checkbox, so a merchandiser hosting a recommendations block or a review widget never sees nine font controls that have nothing to do with it. This is the only section in the theme that uses visible_if.

What the section owns is the frame: where the block mounts, how wide it is, how much air surrounds it, what colour sits behind it, and — for the Forms embed, on request — the --forms-heading-* and --forms-body-* values written onto the element. Shopify Forms renders the form.

Why This Way

Custom properties are the one styling channel a shadow-rooted component offers on purpose. Writing to them from theme-editor settings means the merchandiser's choice travels through a contract the app itself defines, and a future release of the embed that keeps reading those properties keeps honouring the choice. That is the whole argument for the bridge being a {% style %} block fed by settings.

Keeping the host generic was the second decision. One section that renders any @app block with the theme's scaffolding is one section for the merchandising team to learn; the Forms controls are a layer on top of it that only appears when asked for. It costs a little schema — nine settings most instances never show — in exchange for not having a second host section per app.

The price of the closed font list is that adding a face is a code change. That is intended: the faces are licensed and self-hosted, and a select that can only name what the theme carries is the guard. The price of the opt-in is that most instances leave the embed's defaults in place, which is also intended; across the whole theme exactly one instance has the override switched on. The bridge exists for the page that needs it, and stays out of the way on the pages that do not.

Why Not an App

The form is an app, and a first-party one. This build is the theming layer around it, not a replacement for it: Shopify Forms renders the form and writes to the customer record. The alternative was a paid form builder chosen for its styling hooks, which buys typography control at the cost of moving sign-up data into another vendor's system — or shipping the first-party embed with its defaults on every campaign page. A host section that gives the first-party embed the theme's frame and the brand's type keeps the data where it was and the styling in the theme editor.

Implementation Notes

  • The section is "Apps", tagged as a section element, with a single @app block type and disabled_on for the header, aside and footer groups.
  • Width is a select of the theme's own wrapper classes — wrapper--full-padded, wrapper and wrapper--narrow — so the host uses the same page-width conventions as every other section.
  • Padding is two ranges from 0 to 100 px in steps of 2, defaulting to 60, written to --PT and --PB on the section's own id.
  • Every typography setting uses Shopify's visible_if conditional-settings syntax, bound to forms_override_enabled, so the Forms controls appear only when the override is on.
  • The --forms-heading-font and --forms-body-font shorthands are composed inside the {% style %} block from the four part properties, so the embed can read either the parts or the whole.
  • Font pickers are a fixed select of five option values, each carrying its own fallback stack; there is no free-text font field.
  • The heading font-size range runs from 20 to 72 px with a default of 38; the body range from 12 to 32 with a default of 17; heading line-height from 1 to 80 px with a default of 26.
  • One instance in the theme has forms_override_enabled on. Every other instance renders the wrapper, the padding and the background and leaves the block it hosts alone.

Edge Cases

  • A bg_color left blank or set to rgba(0,0,0,0) skips the background rule entirely, so the page's own background shows through rather than a painted transparent layer.
  • With forms_override_enabled off, the {% style %} block emits only padding and background; no --forms-* property is written and the embed uses its own typography.
  • An instance hosting a non-Forms app block with the override switched on writes the --forms-* properties to a shopify-forms-embed element that is not on the page; the rule matches nothing and has no effect.
  • A section with no blocks renders its padded wrapper and nothing inside it, which is how it appears in the theme editor before an app block is added.

Platform Primitives Used

  • Theme app extension blocks — the @app block type is the section's only block, and {% render block %} is the entire mount.
  • Liquid section schema and settings — width, padding, background and nine typography settings, with visible_if hiding the typography group behind one checkbox.
  • CSS custom properties — the styling channel the embed publishes: --forms-heading-* and --forms-body-* set on the host element and read inside its shadow tree.
  • Custom elements<shopify-forms-embed> is a web component, and the section addresses it by its element name.

Where It Runs

142 templates in the theme mount the section — product templates are the largest group, then campaign and programme pages, then the homepage. On the live storefront the section renders on fifteen URLs; one of them carries a Shopify Forms block, and that is the instance with the typography bridge switched on.

What This Demonstrates

  • Merchandiser-authored forms and programme pages — the primary capability: a sign-up form placed and styled from the theme editor on pages that would otherwise be a developer ticket each.
  • Vendor widget remediation and cross-app cart sync — the supported move for styling across a shadow boundary: drive the custom properties the app publishes, from the same controls as the rest of the page.

How We Know

One section file of roughly 290 lines, read from the theme, the 142 templates that mount it, and the live storefront, fetched page by page to see where the section actually renders. Documented from the code; no client-side record of the decision survives. Which custom properties the section writes is stated by its own style block, and the page describes the section rather than the embed.

Related Builds

The Buy-vs-Build Question

Sign-up forms are the clearest case for buying: Shopify's own is first-party and writes straight to the customer record. What you still build is the frame — where it mounts, how it is spaced, what typeface it uses — and that is a section, not an app. Our reasoning on when a form builder earns its fee is in forms and form builders: buy or build?

Provenance & Evidence

  • Client: Three Ships — threeshipsbeauty.ca
  • Surfaces: Landing page, Home page
  • Templates served: 142 templates in the theme mount the section
  • Complexity: Medium — roughly 290 lines, most of it schema
  • Attribution: Deploi-authored. The section, its settings and its style block are ours. It sits in Palo Alto 5.8.0 by Presidio Creative, a paid premium theme the brand licenses, and reuses that theme's wrapper and padding conventions; the form it hosts is Shopify's own app.
  • Status: Live, verified 2026-09-07
  • Evidence: One section file and the 142 templates that mount it, read from the theme, plus a page-by-page fetch of the live storefront; no client working record
  • Confidence: Moderate — documented from the code; no client-side record of the decision survives
  • Primary capability: Merchandiser-authored forms and programme pages

Ready to Put the First-Party Form in Your Own Frame?

If your campaign pages ship Shopify's sign-up form with someone else's typography, the fix is a section, not a subscription. Contact us today and we'll show you what a merchandiser-controlled host looks like around a first-party embed.

More builds