--- name: oro-frontend-dev description: OroCommerce frontend development reference covering storefront themes, back-office themes, layouts, SCSS, JavaScript architecture (Chaplin/Backbone), page components, templates, and asset management. Use when customizing the storefront UI, modifying admin templates, writing JS components, or styling OroCommerce pages. --- # OroCommerce Frontend Developer Reference for frontend/UI development on OroCommerce. Full docs: https://doc.oroinc.com/frontend/ ## Two Theme Systems OroCommerce has two separate theme architectures: | Area | Framework | Templates | JS | Docs | |---|---|---|---|---| | Storefront | OroLayouts (SEO-friendly, no SPA) | Layout YAML + Twig blocks | Chaplin/Backbone | https://doc.oroinc.com/frontend/storefront/ | | Back-office | Placeholders (SPA-like, Twig+Underscore) | Twig extends @OroUI | Chaplin/Backbone | https://doc.oroinc.com/frontend/back-office/ | Themes are NOT interchangeable between storefront and back-office. ## Storefront Theming ### Theme Structure ``` Resources/views/layouts/{theme_name}/ theme.yml # theme definition (label, parent, icon) config/ assets.yml # SCSS entry points jsmodules.yml # JS module registration datagrids.yml # frontend datagrid configs templates/ # Twig block templates page/ # per-page layout overrides ``` ### theme.yml Basic: ```yaml label: 'My Custom Theme' parent: default icon: bundles/mybundle/images/theme-icon.png groups: [commerce] rtl_support: true ``` Full configuration with admin-selectable options: ```yaml label: 'Acme Theme' parent: default groups: [commerce] rtl_support: true configuration: sections: header: label: Header options: promotional_content: label: oro_frontend.theme.default.configuration.header.promotional_content.label type: content_block_selector options: required: false standalone_main_menu: label: oro_frontend.theme.default.configuration.header.standalone_main_menu.label type: checkbox default: checked previews: checked: 'bundles/orofrontend/images/previews/menu-standalone.png' unchecked: 'bundles/orofrontend/images/previews/menu-hamburger.png' product_listing: label: 'oro.product.theme.default.configuration.product_listing.label' options: filters_position: label: 'oro.product.theme.default.configuration.product_listing.filters_position.label' type: radio default: sidebar values: top: 'oro.product.theme.default.configuration.product_listing.filters_position.values.top' sidebar: 'oro.product.theme.default.configuration.product_listing.filters_position.values.sidebar' ``` Configuration option types: `content_block_selector`, `menu_selector`, `checkbox`, `radio`, `select`. ### Layout Updates Layout updates are YAML files that modify page structure: ```yaml layout: actions: - '@setBlockTheme': id: page_container themes: '@MyBundle/layouts/my_theme/page/layout.html.twig' - '@add': id: my_custom_block parentId: page_content blockType: text options: text: '=data["locale"].getLanguage()' - '@remove': id: unwanted_block - '@move': id: existing_block parentId: new_parent ``` Place layout updates in route-specific directories to target individual pages: ``` Resources/views/layouts/{theme}/ layout.yml # global layout (all pages) oro_frontend_root/frontend_root.yml # homepage oro_product_frontend_product_index/product_index.yml # PLP oro_product_frontend_product_view/product_view.yml # PDP oro_checkout_frontend_checkout/checkout.yml # checkout ``` ### Block Types | blockType | Purpose | |---|---| | `container` | Wraps child blocks with a div | | `text` | Static text or expression | | `block` | Custom Twig-rendered block | | `content_block` | References admin-managed content block by `alias` | | `menu` | Renders a navigation menu | ### Layout Data Expressions Access data providers and context variables in YAML options: ```yaml options: vars: # Data provider methods (registered via layout_data_provider tag) topCategories: '=data["category_provider"].getRootCategoriesWithSlugs()' featuredCount: '=data["product_provider"].getFeaturedProductCount()' ctaLinks: '=data["cta_link_provider"].getLinks(data["menu"].getMenu("commerce_main_menu"))' # Context variables (route params, request attributes) category: '=data["category_provider"].getCategoryById(context["category_id"])' ``` ### @addTree Action Add multiple blocks at once: ```yaml - '@addTree': items: footer_menu: blockType: menu options: item: '=data["menu"].getMenu("commerce_footer_links")' depth: 2 tree: page_footer_base: footer_menu: ~ ``` ### @setBlockTheme with Multiple Files ```yaml - '@setBlockTheme': themes: - 'templates/layout.html.twig' - 'templates/page-footer/footer_branding.html.twig' - 'templates/navigation/sidebar_menu.html.twig' ``` ### Twig Block Naming Convention Block names in layout Twig templates follow: `_{blockId}_widget` ```twig {% block _my_custom_block_widget %}
{{ block_widget(block) }}
{% endblock %} ``` ### SCSS / Stylesheets Register SCSS in `config/assets.yml`: ```yaml styles: inputs: - 'bundles/acmetheme/scss/styles.scss' output: 'css/styles.css' ``` Recommended SCSS directory organization: ``` scss/ styles.scss # main entry point (@import all) variables/ _colors.scss # color palette as Sass maps _typography.scss _mixins.scss components/ _product-cards.scss _buttons.scss layout/ _page-header.scss _page-footer.scss _product-view.scss _product-index.scss _checkout.scss navigation/ _sidebar.scss _topbar.scss ``` Color palette using Sass maps (Oro pattern): ```scss @use 'sass:map'; $theme-color-palette: ( 'brand': ( 'primary': #424242, 'secondary': #A89571, ), 'semantic': ( 'success': #005E1F, 'error': #D32F2F, ) ) !default; $color-palette: map.deep-merge($color-palette, $theme-color-palette); ``` Access via `get-var-color('brand', 'primary')`. Build: `php bin/console oro:assets:build`. ### SVG Icons Custom icons go in the theme's icons directory. Register via theme config. ## Back-Office Theming ### Twig Template Overrides Override admin templates by mirroring directory structure under `Resources/views/`. Common base templates to extend: - `@OroUI/actions/index.html.twig` -- list/grid pages - `@OroUI/actions/view.html.twig` -- detail/view pages - `@OroUI/actions/update.html.twig` -- create/edit form pages Use Oro UI macros for consistent rendering: ```twig {% import '@OroUI/macros.html.twig' as UI %} {{ UI.renderProperty('Label', value) }} {{ UI.renderHtmlProperty('HTML Label', htmlValue) }} ``` ### Placeholders Inject content into predefined back-office page regions. ```yaml # Resources/config/oro/placeholders.yml placeholders: oro_view_additional_data: items: my_custom_section: order: 50 template: '@AcmeExample/Placeholder/section.html.twig' applicable: '@acme.placeholder_filter->isApplicable' ``` Common placeholder targets: - `oro_view_additional_data` -- entity view page extra sections - `oro_widget_side_bar` -- sidebar widgets - `oro_title_before` / `oro_title_after` -- title area Conditional rendering via `applicable` filter (service method returning bool). Placeholders are back-office only; use Layout Updates for storefront. ### SCSS Override admin SCSS variables or add new stylesheets via theme settings. ## JavaScript Architecture Built on Chaplin.js (extends Backbone.js). Key concepts: ### Page Components The primary extension mechanism. Define in HTML via data attributes: ```html
``` Component lifecycle: created on `page:update`, disposed on page change. ### JS Module Registration Register modules in `Resources/config/jsmodules.yml`: ```yaml dynamic-imports: mybundle: - mybundle/js/app/components/my-component aliases: jquery$: oroui/js/extend/jquery # Override an existing module with custom implementation map: '*': 'orofrontend/js/app/datafilter/frontend-multiselect-decorator': 'acmetheme/js/custom-multiselect-decorator' ``` ### App Modules Execute code at application startup (auto-initialized on every page): ```yaml app-modules: - acmetheme/js/app/modules/inventory-checker - acmetheme/js/app/modules/checkout-shipping-options ``` ### AMD Module Pattern Oro's JS uses AMD `define()` with the `oroui/js/mediator` for cross-component communication: ```javascript define(function(require) { 'use strict'; const mediator = require('oroui/js/mediator'); const $ = require('jquery'); class MyComponent { constructor() { $(document).ready(() => { mediator.on('checkout:shipping-method:rendered', this.onShippingRendered); }); } onShippingRendered() { // DOM manipulation after Oro renders shipping methods } } new MyComponent(); return MyComponent; }); ``` Key mediator events: `checkout:shipping-method:rendered`, `filters-manager:after-applying-state`, `page:update`, `page:afterChange`. ### Page Events System events during page lifecycle: 1. `page:beforeChange` 2. `page:request` 3. `page:update` -- content is updated, init components 4. `page:afterChange` -- all components initialized ### File Naming Convention ``` js/app/ components/ # PageComponent implementations controllers/ # Chaplin controllers (rare -- PageController handles all) models/ # Backbone models and collections views/ # Backbone views modules/ # App modules (startup code) templates/ # Underscore.js templates (*.html) ``` ## Asset Build ```bash php bin/console oro:assets:build # build all themes php bin/console oro:assets:build --watch # watch mode for dev php bin/console assets:install # symlink public assets ``` Requires Node.js >= 22 and NPM > 10. ## RTL Support Enable Right-to-Left UI via theme configuration. See: https://doc.oroinc.com/frontend/rtl-support/ ## Additional Resources - Storefront how-to guides: https://doc.oroinc.com/frontend/storefront/how-to/ - JS modularity deep-dive: https://doc.oroinc.com/frontend/javascript/javascript-modularity/ - Storefront design/style guide (Figma): https://doc.oroinc.com/frontend/storefront-style-guide/