Enterprise-level CTA system with advanced buttons, button groups, dropdown menus, analytics hooks, and full accessibility. Works in Block Grid, Block List, and as standalone ViewComponent.
$ dotnet add package uAdvancedButtonEnterprise-level CTA system with advanced buttons, button groups, dropdown menus, analytics hooks, and full accessibility.
uab- prefix), no global pollutiondotnet add package uAdvancedButton
On first startup, the package automatically provisions:
uAB)uabAdvancedButton, uabButtonGroup, uabDropdownItemIn your main layout file (e.g. _Layout.cshtml or Master.cshtml), add the CSS in <head> and the JS before </body>:
<head>
...
<link rel="stylesheet" href="/css/advanced-button.css" />
</head>
<body>
...
<script src="/js/advanced-button.js"></script>
</body>
HomePage)You have two options for rendering buttons in your template.
The simplest approach — one line renders everything (buttons, groups, dropdowns):
@using Umbraco.Cms.Core.Models.Blocks
@using Umbraco.Cms.Core.Models.PublishedContent
@using Umbraco.Extensions
@{
IPublishedContent page = Model.Content;
@* Replace "socialMediaButton" with your Block List property alias *@
var buttons = page.Value<BlockListModel>("socialMediaButton");
}
@if (buttons != null)
{
@await Html.PartialAsync("uabRenderButtons", buttons)
}
The uabRenderButtons partial handles both uabAdvancedButton and uabButtonGroup blocks automatically, including nested dropdown items.
If you need full control over the HTML, you can render each block manually. Below is a complete working Master.cshtml example:
@using Umbraco.Cms.Core.Models.Blocks
@using Umbraco.Cms.Core.Models.PublishedContent
@using Umbraco.Extensions
@{
IPublishedContent page = Model.Content;
var buttonBlocks = page.Value<IEnumerable<BlockListItem>>("socialMediaButton");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="~/css/advanced-button.css" rel="stylesheet" />
</head>
<body>
@if (buttonBlocks != null && buttonBlocks.Any())
{
<div class="uab-group uab-group-horizontal uab-group-gap-md uab-group-responsive">
@foreach (var block in buttonBlocks)
{
var c = block.Content;
// ── Content ──
var label = c.Value<string>("buttonLabel") ?? "";
var subLabel = c.Value<string>("subLabel") ?? "";
var extLink = c.Value<string>("externalLink") ?? "";
var intLink = c.Value<IPublishedContent>("internalLink");
var intUrl = intLink?.Url() ?? "";
var mediaFile = c.Value<IPublishedContent>("mediaLink");
var mediaUrl = mediaFile?.Url() ?? "";
var anchorId = c.Value<string>("anchorTarget") ?? "";
var iconImg = c.Value<IPublishedContent>("iconMedia");
var iconUrl = iconImg?.Url() ?? "";
var iconPos = c.Value<string>("iconPosition") ?? "left";
var openNewTab = c.Value<bool>("openInNewTab");
var ariaLabel = c.Value<string>("ariaLabel") ?? "";
// ── Analytics ──
var eventName = c.Value<string>("analyticsEventName") ?? "";
var eventPayload = c.Value<string>("analyticsPayload") ?? "";
// ── Behavior ──
var isLoading = c.Value<bool>("loadingState");
var isDisabled = c.Value<bool>("disabledState");
var hasConfirm = c.Value<bool>("confirmationDialog");
var confirmText = c.Value<string>("confirmationText") ?? "Are you sure?";
var scrollTo = c.Value<bool>("scrollToAnchor");
var isModal = c.Value<bool>("modalTrigger");
var modalId = c.Value<string>("modalTargetId") ?? "";
// ── Dropdown ──
var isDropdown = c.Value<bool>("isDropdown");
var dropdownBlocks = c.Value<IEnumerable<BlockListItem>>("dropdownItems");
// ── Style ──
var size = c.Value<string>("sizeVariant") ?? "md";
var style = c.Value<string>("styleVariant") ?? "primary";
var mode = c.Value<string>("modeSelector") ?? "solid";
var radius = c.Value<string>("borderRadius") ?? "md";
var shadow = c.Value<bool>("shadowEnabled");
var hover = c.Value<string>("hoverAnimation") ?? "none";
var customClass = c.Value<string>("customCssClass") ?? "";
var fullWidth = c.Value<bool>("fullWidth");
var alignment = c.Value<string>("alignment") ?? "left";
var spacing = c.Value<string>("spacingPreset") ?? "md";
// ── Resolve URL ──
var url = !string.IsNullOrEmpty(intUrl) ? intUrl
: !string.IsNullOrEmpty(extLink) ? extLink
: !string.IsNullOrEmpty(mediaUrl) ? mediaUrl
: scrollTo && !string.IsNullOrEmpty(anchorId) ? $"#{anchorId}"
: "";
var hasLink = !string.IsNullOrEmpty(url);
// ── Tag: dropdown always renders as <button> ──
var useAnchor = hasLink && !isDisabled && !isDropdown;
// ── Sanitize custom class ──
var safeClass = System.Text.RegularExpressions.Regex.Replace(
customClass, @"[^a-zA-Z0-9\s\-_]", "");
// ── Build CSS classes ──
var cls = $"uab-btn uab-size-{size} uab-style-{style} uab-mode-{mode} uab-radius-{radius}";
if (shadow) { cls += " uab-shadow"; }
if (fullWidth) { cls += " uab-full-width"; }
if (isLoading) { cls += " uab-loading"; }
if (isDisabled){ cls += " uab-disabled"; }
if (hover != "none") { cls += $" uab-hover-{hover}"; }
if (isDropdown){ cls += " uab-dropdown-trigger"; }
if (!string.IsNullOrEmpty(safeClass)) { cls += $" {safeClass}"; }
var wrapperCls = $"uab-wrapper uab-align-{alignment} uab-spacing-{spacing}";
var isIconOnly = iconPos == "icon-only";
<div class="@wrapperCls">
@if (useAnchor)
{
<a href="@url"
class="@cls"
@Html.Raw(openNewTab ? "target=\"_blank\" rel=\"noopener noreferrer\"" : "")
@Html.Raw(!string.IsNullOrEmpty(ariaLabel) ? $"aria-label=\"{Html.Encode(ariaLabel)}\"" : "")
@Html.Raw(!string.IsNullOrEmpty(eventName) ? $"data-event=\"{Html.Encode(eventName)}\"" : "")
@Html.Raw(!string.IsNullOrEmpty(eventPayload) ? $"data-payload='{Html.Encode(eventPayload)}'" : "")
@Html.Raw(hasConfirm ? $"data-confirm=\"{Html.Encode(confirmText)}\"" : "")
@Html.Raw(scrollTo && !string.IsNullOrEmpty(anchorId) ? $"data-scroll-to=\"{Html.Encode(anchorId)}\"" : "")
@Html.Raw(isModal && !string.IsNullOrEmpty(modalId) ? $"data-modal-target=\"{Html.Encode(modalId)}\"" : "")>
@if (isLoading)
{
<span class="uab-spinner" aria-hidden="true"></span>
<span class="uab-sr-only">Loading...</span>
}
else
{
@if (!string.IsNullOrEmpty(iconUrl) && (iconPos == "left" || isIconOnly))
{
<img src="@iconUrl" alt="" class="uab-icon uab-icon-left" loading="lazy" aria-hidden="true" />
}
@if (!isIconOnly)
{
<span class="uab-label">@label</span>
@if (!string.IsNullOrEmpty(subLabel))
{
<span class="uab-sub-label">@subLabel</span>
}
}
@if (!string.IsNullOrEmpty(iconUrl) && iconPos == "right")
{
<img src="@iconUrl" alt="" class="uab-icon uab-icon-right" loading="lazy" aria-hidden="true" />
}
}
</a>
}
else
{
<button type="button"
class="@cls"
@Html.Raw(isDisabled ? "disabled aria-disabled=\"true\"" : "")
@Html.Raw(!string.IsNullOrEmpty(ariaLabel) ? $"aria-label=\"{Html.Encode(ariaLabel)}\"" : "")
@Html.Raw(!string.IsNullOrEmpty(eventName) ? $"data-event=\"{Html.Encode(eventName)}\"" : "")
@Html.Raw(!string.IsNullOrEmpty(eventPayload) ? $"data-payload='{Html.Encode(eventPayload)}'" : "")
@Html.Raw(hasConfirm ? $"data-confirm=\"{Html.Encode(confirmText)}\"" : "")
@Html.Raw(scrollTo && !string.IsNullOrEmpty(anchorId) ? $"data-scroll-to=\"{Html.Encode(anchorId)}\"" : "")
@Html.Raw(isModal && !string.IsNullOrEmpty(modalId) ? $"data-modal-target=\"{Html.Encode(modalId)}\"" : "")
@Html.Raw(isDropdown ? "aria-haspopup=\"true\" aria-expanded=\"false\"" : "")>
@if (isLoading)
{
<span class="uab-spinner" aria-hidden="true"></span>
<span class="uab-sr-only">Loading...</span>
}
else
{
@if (!string.IsNullOrEmpty(iconUrl) && (iconPos == "left" || isIconOnly))
{
<img src="@iconUrl" alt="" class="uab-icon uab-icon-left" loading="lazy" aria-hidden="true" />
}
@if (!isIconOnly)
{
<span class="uab-label">@label</span>
@if (!string.IsNullOrEmpty(subLabel))
{
<span class="uab-sub-label">@subLabel</span>
}
}
@if (!string.IsNullOrEmpty(iconUrl) && iconPos == "right")
{
<img src="@iconUrl" alt="" class="uab-icon uab-icon-right" loading="lazy" aria-hidden="true" />
}
@if (isDropdown)
{
<span class="uab-caret" aria-hidden="true"></span>
}
}
</button>
}
@* ── Dropdown Menu ── *@
@if (isDropdown && dropdownBlocks != null && dropdownBlocks.Any())
{
<ul class="uab-dropdown-menu" role="menu" aria-hidden="true">
@foreach (var ddBlock in dropdownBlocks)
{
var dd = ddBlock.Content;
var ddLabel = dd.Value<string>("label") ?? "";
var ddExtLink = dd.Value<string>("externalLink") ?? "";
var ddIntLink = dd.Value<IPublishedContent>("internalLink");
var ddUrl = ddIntLink?.Url() ?? ddExtLink;
var ddIcon = dd.Value<IPublishedContent>("iconMedia");
var ddIconUrl = ddIcon?.Url() ?? "";
<li role="none">
<a href="@(string.IsNullOrEmpty(ddUrl) ? "#" : ddUrl)"
class="uab-dropdown-item" role="menuitem" tabindex="-1">
@if (!string.IsNullOrEmpty(ddIconUrl))
{
<img src="@ddIconUrl" alt="" class="uab-dropdown-icon" loading="lazy" />
}
<span>@Html.Encode(ddLabel)</span>
</a>
</li>
}
</ul>
}
</div>
}
</div>
}
<script src="~/js/advanced-button.js"></script>
</body>
</html>
Key rules for manual rendering:
- Dropdown buttons must render as
<button>, never<a>— usehasLink && !isDisabled && !isDropdownto decide the tag- Always include
<span class="uab-caret">inside dropdown buttons for the visual arrow indicator- Use
uab-icon-left/uab-icon-rightCSS classes on icon<img>elements for proper spacing- Include
data-scroll-toon<button>elements too (not just<a>) so scroll-to-anchor works without a link- Always use
Html.Encode()for user-provided attribute values andHtml.Raw()for the conditional attribute wrappers
The dropdown renders a <button> with aria-haspopup, aria-expanded, and a <ul> menu with full keyboard navigation (Arrow keys, Home, End, Escape, Tab).
| Group | Alias | Type | Description |
|---|---|---|---|
| Content | buttonLabel | Text | Main button text (required) |
| Content | subLabel | Text | Secondary text below label |
| Content | iconMedia | Media Picker | SVG or image icon |
| Content | iconPosition | Dropdown | left, right, icon-only |
| Content | internalLink | Content Picker | Link to Umbraco page |
| Content | externalLink | Text | External URL |
| Content | mediaLink | Media Picker | Link to media file |
| Content | anchorTarget | Text | Anchor ID (without #) |
| Content | openInNewTab | Toggle | Open in new tab |
| Content | ariaLabel | Text | Screen reader label |
| Content | analyticsEventName | Text | Analytics event name |
| Content | analyticsPayload | TextArea | Analytics JSON payload |
| Behavior | loadingState | Toggle | Show spinner |
| Behavior | disabledState | Toggle | Disable button |
| Behavior | confirmationDialog | Toggle | Confirm before action |
| Behavior | confirmationText | Text | Confirmation message |
| Behavior | scrollToAnchor | Toggle | Smooth scroll on click |
| Behavior | modalTrigger | Toggle | Trigger modal on click |
| Behavior | modalTargetId | Text | Target modal element ID |
| Layout | fullWidth | Toggle | 100% width |
| Layout | alignment | Dropdown | left, center, right |
| Layout | spacingPreset | Dropdown | none, xs, sm, md, lg, xl |
| Style | sizeVariant | Dropdown | xs, sm, md, lg, xl |
| Style | styleVariant | Dropdown | primary, secondary, success, danger, neutral, custom |
| Style | modeSelector | Dropdown | solid, outline, ghost, gradient |
| Style | borderRadius | Dropdown | none, sm, md, lg, xl, full |
| Style | shadowEnabled | Toggle | Drop shadow |
| Style | hoverAnimation | Dropdown | none, scale, lift, glow, pulse, slide |
| Style | customCssClass | Text | Extra CSS class |
| Dropdown | isDropdown | Toggle | Enable dropdown mode |
| Dropdown | dropdownItems | Block List | Dropdown menu items |
| Alias | Type | Description |
|---|---|---|
label | Text | Item label |
internalLink | Content Picker | Internal page link |
externalLink | Text | External URL |
iconMedia | Media Picker | Optional icon |
| Group | Alias | Type | Description |
|---|---|---|---|
| Buttons | buttons | Block List | Nested Advanced Buttons |
| Layout | groupLayout | Dropdown | horizontal, vertical |
| Layout | wrapEnabled | Toggle | Allow wrapping |
| Layout | equalWidth | Toggle | Equal-width buttons |
| Layout | gapSize | Dropdown | none, xs, sm, md, lg, xl |
| Layout | groupAlignment | Dropdown | left, center, right |
| Layout | responsiveStacking | Toggle | Stack on mobile |
On build, the .targets file copies package assets into two locations in the consumer project:
| File | Copied to (project root) | Also copied to (App_Plugins mirror) |
|---|---|---|
AdvancedButton.cshtml | Views/Partials/ | App_Plugins/uAdvancedButton/content/Views/Partials/ |
_ButtonInner.cshtml | Views/Partials/ | App_Plugins/uAdvancedButton/content/Views/Partials/ |
ButtonGroup.cshtml | Views/Partials/ | App_Plugins/uAdvancedButton/content/Views/Partials/ |
uabRenderButtons.cshtml | Views/Partials/ | App_Plugins/uAdvancedButton/content/Views/Partials/ |
Error.cshtml | Views/Partials/ | App_Plugins/uAdvancedButton/content/Views/Partials/ |
NoContent.cshtml | Views/Partials/ | App_Plugins/uAdvancedButton/content/Views/Partials/ |
advanced-button.css | wwwroot/css/ | App_Plugins/uAdvancedButton/content/wwwroot/css/ |
advanced-button.js | wwwroot/js/ | App_Plugins/uAdvancedButton/content/wwwroot/js/ |
On dotnet clean, the App_Plugins directory and wwwroot/css/advanced-button.css + wwwroot/js/advanced-button.js are removed.
The package outputs data-event and data-payload attributes on buttons. No tracking library is bundled — attach your own listener:
document.addEventListener("uab:analytics", function (e) {
// e.detail.event — the event name from "analyticsEventName"
// e.detail.payload — parsed JSON from "analyticsPayload"
gtag("event", e.detail.event, e.detail.payload);
});
MIT — © 2026 Atharva IT Services Pvt. Ltd.
For support, feature requests, or bug reports:
Copyright © Atharva IT Services Pvt. Ltd.
This package is free to use for the Umbraco community.
Atharva IT Services Pvt. Ltd. a global software development company in Ahmedabad, excels in e-commerce solutions, web development, and software testing.
Made with ❤️ by Atharva IT Services