Yoyo.Umbraco.LivePreview
0.0.4
dotnet add package Yoyo.Umbraco.LivePreview --version 0.0.4
NuGet\Install-Package Yoyo.Umbraco.LivePreview -Version 0.0.4
<PackageReference Include="Yoyo.Umbraco.LivePreview" Version="0.0.4" />
<PackageVersion Include="Yoyo.Umbraco.LivePreview" Version="0.0.4" />
<PackageReference Include="Yoyo.Umbraco.LivePreview" />
paket add Yoyo.Umbraco.LivePreview --version 0.0.4
#r "nuget: Yoyo.Umbraco.LivePreview, 0.0.4"
#:package Yoyo.Umbraco.LivePreview@0.0.4
#addin nuget:?package=Yoyo.Umbraco.LivePreview&version=0.0.4
#tool nuget:?package=Yoyo.Umbraco.LivePreview&version=0.0.4
Yoyo Umbraco Live Preview
Yoyo Umbraco Live Preview adds an interactive preview workspace to Umbraco backoffice.
It renders your page inside an iframe, listens to in-progress editor values, posts them to a preview API endpoint, and returns updated HTML without saving/publishing.
To make regions editable in the preview, wrap markup with the <preview> tag helper. The package links each preview region to the correct property editor so editors can be focused, toggled, or updated directly from the preview overlay.
Features
- Real-time page rendering without save or publish.
- Overlay interaction from preview to property editors.
- Supports primitive properties, pickers, media, rich text, block editors, and nested content structures.
- Supports preview breakpoints (mobile/tablet/laptop/desktop/fill).
- Supports strongly typed generated models and custom view models.
Compatibility
- Umbraco 17
- .NET 10
Install
Install from NuGet:
dotnet add package Yoyo.Umbraco.LivePreview
Register The Package
Register the package in your Umbraco builder pipeline.
Use AddLivePreview in your Umbraco builder chain.
builder.CreateUmbracoBuilder()
.AddBackOffice()
.AddWebsite()
.AddComposers()
.AddUmbracoFormsCustomProviders()
.AddLivePreview()
.SetContentLastChanceFinder<ContentLastChanceFinder>()
.Build();
Add The Tag Helper
Add the tag helper import to your view imports file:
@addTagHelper *, Yoyo.Umbraco.LivePreview
Then use <preview> around markup you want to make interactive in live preview.
How The <preview> Tag Helper Works
The tag helper injects lightweight comment markers around your markup. The preview overlay script reads these markers and associates each region with a property payload.
The payload tracks:
- document key
- path within the editable data tree
- editor alias
- optional index
- optional toggle behavior
- optional empty-value state
This is why placement and binding style matter.
<preview> Attributes
for: model expression to bind a region to a property/object.parent: optional parent context used for nested and alias-based resolution.alias: explicit property alias lookup (commonly used for nested block content values).index: explicit index for list/array item mapping.toggle: toggles checkbox/radio/swatch-like editors instead of just focus.
Usage Patterns
1) Basic property binding
<preview for="@Model.TextBox">
<div>@Model.TextBox</div>
</preview>
Use this for standard scalar properties.
2) Rich text and HTML content
<preview for="@Model.RichText">
<div>@Model.RichText</div>
</preview>
3) Full object binding
<preview for="@Model">
<div>...</div>
</preview>
Use this when the entire object has already been tracked and you want a single region tied to that object.
4) Explicit alias with parent (nested block/content scenarios)
<preview parent="@block.Content" alias="text">
<div>@block.Content.Value("text")</div>
</preview>
Use this when you know the parent object and the exact property alias.
5) Collection item binding with explicit index
@{
var index = 0;
}
@foreach (var item in Model.MultipleTextString)
{
<preview for="@Model.MultipleTextString" index="@index">
<li>@item</li>
</preview>
index++;
}
This is the safest pattern for primitive lists.
6) Toggle behavior (checkbox/radio/swatch style)
<preview for="@Model.Toggle" toggle="true">
<div>@Model.Toggle</div>
</preview>
Use toggle="true" when click behavior should change value, not only focus the editor.
Why Primitive Collections Need Special Care
For primitive values (string/number/bool), many items can have equal values. Auto matching can become ambiguous when duplicates exist.
Recommended approach:
- Always provide explicit
indexin loops for primitive collections. - Prefer binding to the parent collection with
for="@Model.Property"plusindex. - Do not rely on value-based matching when duplicate values are possible.
Good:
<preview for="@Model.Tags" index="@index">
<span>@tag</span>
</preview>
Less reliable for duplicate primitive values:
<preview for="@tag">
<span>@tag</span>
</preview>
Empty State Regions
If a property is empty, still render a preview wrapper so the editor can be opened from the preview:
<preview for="@Model.MultipleMedia">
<div>None</div>
</preview>
This keeps the overlay discoverable even with no value.
Using Generated Models Vs Custom View Models
Generated models (ModelsBuilder)
No special mapping is usually required if your views already inherit generated models.
Custom view models (non-generated)
You must map document type aliases to view model factories using package options.
This allows live preview to resolve the same view model shape your site expects.
Example:
builder.CreateUmbracoBuilder()
.AddBackOffice()
.AddWebsite()
.AddComposers()
.AddUmbracoFormsCustomProviders()
.AddLivePreview(options =>
{
// Factories that use Create(IPublishedContent, HttpRequest)
options.MapViewModel("articleListingPage", (content, request, sp) =>
sp.GetRequiredService<ArticleListingPageViewModelFactory>().Create(content, request));
options.MapViewModel("articleCategoryPage", (content, request, sp) =>
sp.GetRequiredService<ArticleListingPageViewModelFactory>().Create(content, request));
options.MapViewModel("projectListingPage", (content, request, sp) =>
sp.GetRequiredService<ProjectListingPageViewModelFactory>().Create(content, request));
// Factories that only need IPublishedContent (no request)
options.MapViewModel("articlePage", (content, sp) =>
sp.GetRequiredService<ArticlePageViewModelFactory>().Create(content));
options.MapViewModel("contactPage", (content, sp) =>
sp.GetRequiredService<ContactPageViewModelFactory>().Create(content));
options.MapViewModel("errorPage", (content, sp) =>
sp.GetRequiredService<ErrorPageViewModelFactory>().Create(content));
options.MapViewModel("projectPage", (content, sp) =>
sp.GetRequiredService<ProjectPageViewModelFactory>().Create(content));
// Generic PageViewModelFactory pattern
options.MapViewModel("contentPage", (content, sp) =>
sp.GetRequiredService<PageViewModelFactory>().Create<PageViewModel>(content));
options.MapViewModel("homePage", (content, sp) =>
sp.GetRequiredService<PageViewModelFactory>().Create<PageViewModel>(content));
})
.SetContentLastChanceFinder<ContentLastChanceFinder>()
.Build();
Breakpoint Preview
The preview workspace includes built-in breakpoints:
- Mobile
- Tablet
- Laptop
- Desktop
- Fill
Editors can switch breakpoints to validate responsive behavior while still editing content in place.
JavaScript And CSS Integration
The preview runtime exposes helpers and signals so front-end code can respond safely when preview HTML is re-rendered.
Razor / partial preview mode check
The package exposes an HttpContext extension method for server-side checks:
If Yoyo.LivePreview.Extensions is already imported in your _ViewImports.cshtml (or via global using), you can call this directly in partials without adding another @using.
@{
var isPreviewMode = Context.IsPreviewMode();
}
@if (isPreviewMode)
{
<div class="preview-only-hint">Preview mode</div>
}
Use this in partials or views when markup needs to differ between live preview rendering and normal front-end rendering.
window.previewMode
When a page is rendered inside live preview, the package sets:
window.previewMode = true;
Use this to branch behavior that should only run inside live preview.
if (window.previewMode) {
// preview-specific behavior
}
Update lifecycle events
During DOM patching, the preview runtime emits custom bubbling events:
preview-before-updated: fired on each element before it is updated.preview-updated: fired on each element after it is updated.
Example listeners:
document.addEventListener("preview-before-updated", (event) => {
// cleanup or capture state before morphing
const el = event.target;
});
document.addEventListener("preview-updated", (event) => {
// re-bind behaviors after morphing
const el = event.target;
});
Tip: if you initialize third-party UI widgets, re-run initialization logic from preview-updated.
window.previewService
The preview runtime exposes a global previewService for controlling how morph updates treat specific elements.
Available methods:
ignoreElement(el): prevent an element from being replaced/discarded.unignoreElement(el): remove ignore behavior from that element.preserveAttributes(el, attributes): keep selected attributes from the live element.preserveClasses(el, classes): keep selected classes from the live element.preserveStyles(el, styles): keep selected inline style properties from the live element.
Example:
const slider = document.querySelector(".my-slider");
if (slider && window.previewService) {
window.previewService.ignoreElement(slider);
window.previewService.preserveAttributes(slider, [
"aria-expanded",
"data-state",
]);
window.previewService.preserveClasses(slider, ["is-open", "is-active"]);
window.previewService.preserveStyles(slider, ["transform", "height"]);
}
CSS: html.preview-mode
In preview rendering, the <html> element gets the preview-mode class.
Use it to scope preview-specific CSS:
html.preview-mode .cookie-banner {
display: none;
}
html.preview-mode .debug-outline {
opacity: 1;
}
Depending on breakpoint configuration, the runtime may also toggle hide-scrollbar on <html>.
Backoffice Breakpoint Customization
You can add, override, disable, or reorder breakpoints through your umbraco-package.json extension manifest.
Example:
{
"$schema": "../../umbraco-package-schema.json",
"name": "My Custom Preview Setup",
"extensions": [
{
"type": "yoyo-preview-breakpoints",
"alias": "yoyo.preview.breakpoints",
"name": "Preview breakpoints",
"meta": {
"breakpoints": [
{
"id": "laptop",
"label": "My laptop",
"disabled": false
}
]
}
}
]
}
How merge behavior works:
- Matching
idvalues override defaults. - New
idvalues add custom breakpoints. disabled: truehides a breakpoint.weightcontrols sort order.
Default breakpoints
[
{
id: "mobile-portrait",
label: "Mobile",
minWidth: "360px",
maxWidth: "375px",
aspectRatio: "375 / 720",
icon: "icon-mobile",
scrollbar: false,
weight: 10,
},
{
id: "tablet-portrait",
label: "Tablet",
minWidth: "640px",
maxWidth: "768px",
aspectRatio: "768 / 1024",
icon: "icon-ipad",
scrollbar: false,
weight: 20,
},
{
id: "laptop",
label: "Laptop",
minWidth: "1024px",
maxWidth: "1600px",
aspectRatio: "16 / 9",
icon: "icon-laptop",
scrollbar: true,
weight: 30,
},
{
id: "desktop",
label: "Desktop",
minWidth: "1600px",
maxWidth: "1920px",
aspectRatio: "16 / 9",
icon: "icon-display",
scrollbar: true,
weight: 40,
},
{
id: "fill",
label: "Fill",
minWidth: "360px",
maxWidth: "100%",
height: "100%",
aspectRatio: "auto",
disablePadding: true,
icon: "icon-fullscreen-alt",
scrollbar: true,
weight: 100,
},
];
Breakpoint option reference
id(string, required): unique key. Matching ids override existing entries.label(string): tab label shown in the preview UI.minWidth(string): minimum viewport box width.maxWidth(string): maximum viewport box width.height(string): explicit viewport height (optional).aspectRatio(string): CSS aspect-ratio value for the viewport.icon(string): Umbraco backoffice icon name.iconRotate(string): optional CSS rotate value for icon orientation.disablePadding(boolean): removes preview container padding when true.scrollbar(boolean): controlshide-scrollbarclass behavior.weight(number): lower values render earlier in the tab list.disabled(boolean): hides the breakpoint when true.
Icon names come from standard Umbraco backoffice icons.
Troubleshooting
Preview does not appear
- Ensure the document has a template.
- Confirm the package is registered in the Umbraco builder pipeline.
- Confirm App_Plugins assets are present.
Overlay appears but editor does not focus
- Verify
for/parent/aliasmapping reflects actual property structure. - For collections, provide explicit
index. - For nested blocks, prefer
parent + aliaswhen needed.
Wrong editor opens from list items
- Add explicit
indexfor primitive or repeating items. - Ensure loop index increments correctly.
Links
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Umbraco.Cms.Api.Common (>= 17.6.0)
- Umbraco.Cms.Api.Management (>= 17.6.0)
- Umbraco.Cms.DevelopmentMode.Backoffice (>= 17.6.0)
- Umbraco.Cms.Web.Common (>= 17.6.0)
- Umbraco.Cms.Web.Website (>= 17.6.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|