What is a Component in Noir#
A Component is a platform-rendered building block included by ComponentsList.liquid.Components are arranged in page sections (Header / Body / Footer) and are rendered dynamically based on the component model:The renderer resolves a template include name using:Components_<model.Name>_<model.View>
So component template naming must remain consistent with what the platform sends.
Where components live#
Each component lives under:Most components follow this structure:Components/<Name>/Default.liquid
The Liquid template (markup + bindings).
Default.js (optional)
Client-side behavior for the component.
Components/<Name>/Default.json (optional)
Translation keys used by model.Translations.*.
Note
Even if a component doesn't currently need JavaScript or translations, we still keep the standard file set for consistency:Default.liquid (required)
Default.js (kept even if empty/minimal)
Default.json (kept even if empty/minimal)
This makes components easier to maintain, extend, and document, and keeps the theme structure uniform across all component folders.
Model conventions (common fields)#
Component templates receive a model object.Settings#
model.Settings.Id
Used for a deterministic DOM id. Example pattern:
{% assign id = model.Settings.Id %}
<section id="comp-{{ id }}">
...
</section>
model.Settings.CssClass (optional)
Additional CSS classes for the outer wrapper. Common pattern is to apply only when non-empty and not (UNDEFINED).
Translations#
If a component has a Default.json, keys are available as:Use translations for UI labels or messages that must be localized.Reusable slots#
A component that renders a reusable exposes one setting per reusable, named after the reusable it
renders by default with a Reusable suffix, for example model.Settings.ProductGridItemReusable or
model.Settings.LoginReusable. The value is the name of another reusable in the theme. When the
setting is empty, the view renders the reusable it always has.The template renders the slot with render and a replace_with argument:{% render 'Reusables\\ProductGridItem\\Default', replace_with: model.Settings.ProductGridItemReusable, product: product, modelId: id %}
The path is the default reusable, and replace_with is the slot setting.
The other named arguments are passed to whichever reusable renders. replace_with itself is not.
replace_with only applies to a render of a Reusables path with named arguments. Anywhere else,
render behaves as standard Liquid.
The default renders when the setting is empty, is not a valid name (letters, digits, _ and -
only), or names a reusable without a Default.liquid view.
The selected reusable must accept the arguments the view passes. The platform does not check this,
so pick a reusable written for the same inputs as the default.
Components that switch between two reusables, such as the standard and classic product cards, have a
slot for each, so the global product card style setting keeps choosing between them.When you add a reusable to a component view, render it through a slot and add the matching
<Name>Reusable setting to the component.
JS conventions#
Where JS is loaded from#
/Themes/Noir/componentscripts.js (component/reusable scripts compiled/aggregated)
theme.js (theme-wide utilities + Alpine stores)
This implies the component's JS must work with the global execution model and the script loading order.Alpine-friendly globals#
Many components use Alpine directives (x-data, [x-show], x-transition]) and rely on:global objects exposed by each component script, and/or
Alpine stores defined in theme.js.
Rule: cross-component state belongs in Alpine stores (theme-wide).
Styling conventions#
Noir uses Tailwind as the primary styling approach:prefer Tailwind utility classes in Liquid templates
use CSS variables for theme tokens when available
use Custom CSS only for small overrides
avoid adding new bespoke CSS unless necessary
Safe editing rules#
Do not rename component folders lightly (model.Name must match the folder/template naming convention).
Keep the Default view naming consistent unless there is an explicit need for a new view.
If you introduce new JS behavior, ensure it doesn't conflict with existing global utilities or Alpine stores.
If you add new translation keys, ensure the template uses model.Translations.* consistently.
Modified at 2026-10-29 07:57:56