This post walks through a practical “66% / 33%” section built for Visual Builder using the composition tag helpers: <epi-grid>, <epi-row>, <epi-column>, and <epi-component />. Visual Builder is Optimizely’s layout-and-composition editing experience (originally introduced for CMS SaaS, but the concepts carry over).
What you’re building
A section type that renders:
- A Bootstrap
container - One
row - Two columns:
- Left:
col-md-9 - Right:
col-md-3
- Left:
- Each column can contain multiple components (blocks) dropped in Visual Builder.
Step 1: Make sure tag helpers are available in your views
Add the Optimizely tag helpers to Views/_ViewImports.cshtml:
*, EPiServer.Cms.AspNetCore.TagHelpers
This is the standard ASP.NET Core pattern: adding @addTagHelper in _ViewImports.cshtml makes them available to all views under Views/.
Step 2: Create the section content type
Create a new section model. In CMS 13, sections are typically implemented as block types deriving from EPiServer.VisualBuilder.SectionData and using its Layout property.
Example:
using EPiServer.DataAnnotations;using EPiServer.VisualBuilder;namespace alloy13preview.Models.Experiences{ [ContentType( GUID = "B4A4C0C2-5B89-4F17-9B10-7AE04A0A9E6D", DisplayName = "Two columns (wide left)", GroupName = "Sections")] public class TwoColumnWideLeftSection : SectionData { public override void SetDefaultValues(ContentType contentType) { base.SetDefaultValues(contentType); // Layout.Type should be "grid" for the grid/row/column tag helpers Layout = new Layout("grid"); var row = new LayoutStructureNode("row"); var left = new LayoutStructureNode("column"); left.DisplaySettings["col"] = "col-md-9"; var right = new LayoutStructureNode("column"); right.DisplaySettings["col"] = "col-md-3"; row.Nodes.Add(left); row.Nodes.Add(right); Layout.Nodes.Add(row); } }}
Why store col-md-* in DisplaySettings instead of hardcoding in Razor?
Because DisplaySettings lives on the composition node, meaning it can be:
- defaulted programmatically (as above),
- later driven by editor-visible “style choices” if you wire up display templates,
- and applied cleanly via
<epi-styles>.
Step 3: Create the Razor view using composition tag helpers
Create something like:
Views/Shared/Sections/TwoColumnWideLeftSection.cshtml(your project may vary)
alloy13preview.Models.Experiences.TwoColumnWideLeftSection<epi-grid class="container"> <epi-row class="row"> <epi-column class="col-12"> <epi-styles> <epi-style name="col"> <epi-style-map value="col-md-9" class="col-md-9" /> <epi-style-map value="col-md-3" class="col-md-3" /> </epi-style> </epi-styles> <epi-component /> </epi-column> </epi-row></epi-grid>
Key points
- Keep
col-12columns so the layout is same on mobile. <epi-styles>must be inside the<epi-column>tag so it can apply CSS classes to that column tag helper.- The style map values (
col-md-9,col-md-3) must match what you store inDisplaySettings["col"].
Common pitfalls
1) “My column width classes don’t apply”
Usually one of these:
- Your
DisplaySettingskey doesn’t match the<epi-style name="...">. - Your
DisplaySettingsvalue doesn’t match any<epi-style-map value="...">. - You put
<epi-styles>outside the parent tag helper you want to style.
2) Bootstrap gutter/layout confusion
Remember:
container→ fixed widthsrow→ establishes the grid row- columns need
col-*classes to participate in the grid
Extending this pattern (full width, 3 columns, etc.)
Once you’re comfortable with “row → N columns”, you can:
- Create
FullWidthSectionwith a singlecolumnnode and one<epi-column>. - Create
ThreeColumnSectionwith threecolumnnodes and three<epi-column>tags. - Create variants like:
TwoColumnEqualSection(col-md-6/col-md-6)TwoColumnWideRightSection(col-md-3/col-md-9)
This keeps each section simple, predictable, and editor-friendly.
Why this is a good CMS 12 → CMS 13 comparison point
In CMS 12, most teams model this as a section block with multiple ContentAreas (Left/Right) and custom editor hints. In CMS 13 Visual Builder, the layout tree plus composition tag helpers becomes the primary mechanism, and DisplaySettings gives you a structured way to flow editor choices into CSS without inventing your own metadata system.