Add D-prefixed Blazor wrappers for every daisyUI 5 component

Components/Daisy/ wraps all 69 components in daisyUI's llms.txt, grouped by
daisyUI's categories (Actions, DataDisplay, Navigation, Feedback, DataInput,
Layout, Mockup), with part components where daisyUI has repeating parts (DTab,
DMenuItem, DTimelineItem, ...).
- Each enum maps to whole literal class names in a switch, so Tailwind's scanner
  generates them. Nothing is concatenated.
- Value-holding fields derive from DInputBase<TValue>, Blazor's InputBase, so
  @bind-Value and EditForm validation work. Invalid fields get the component's
  *-error class.
- Open and active state lives in C# (@bind-IsOpen, @bind-Active) and is rendered
  with daisyUI's *-open modifiers. DDropdown adds an outside-click layer and
  Escape. No component uses JS interop.
- The one exception is DCalendar, which wraps the Cally web component, vendored
  as wwwroot/vendor/cally.js (0.9.2, MIT).

/styleguide (not linked) renders every component in all its colours, sizes and
states, with a theme and hue picker that previews without saving.

neo.css follows what the styleguide showed:
- select keeps daisyUI's arrow;
- skeleton-text isn't sunk;
- the phone mockup and dock are raised;
- active menu items and OTP digits are inset;
- a joined accordion is raised as one block.
The primary, secondary and accent colours are deeper, so they still read as
text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-03 12:01:42 +02:00
1 parent 8e03d12a3e
commit 90e4b9200c
127 files changed
+6962 -17

No files matched your search

@@ -0,0 +1,23 @@
@inherits DComponentBase
@* One crumb of a DBreadcrumbs: <li><a> with Href, otherwise <li><span> (the current page). class and other
attributes go on the <a>/<span>. *@
<li>
@if (Href is null)
{
<span @attributes="AdditionalAttributes" class="@Cx("inline-flex items-center gap-2", Class)">
@ChildContent
</span>
}
else
{
<a @attributes="AdditionalAttributes" href="@Href" class="@Class">
@ChildContent
</a>
}
</li>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Href { get; set; }
}
@@ -0,0 +1,13 @@
@inherits DComponentBase
@* daisyUI Breadcrumbs: https://daisyui.com/components/breadcrumbs/ . Holds DBreadcrumbItem children; a max-width
(e.g. class="max-w-xs") makes the list scroll. *@
<div @attributes="AdditionalAttributes" class="@Cx("breadcrumbs", Class)">
<ul>
@ChildContent
</ul>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
+21
View File
@@ -0,0 +1,21 @@
@inherits DComponentBase
@* daisyUI Dock: https://daisyui.com/components/dock/ . A bar fixed to the bottom of the viewport holding DDockItem
children; add class="relative" to place it in the flow instead. *@
<div @attributes="AdditionalAttributes" class="@Cx("dock", SizeClass, Class)">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DSize Size { get; set; }
string SizeClass => Size switch
{
DSize.Xs => "dock-xs",
DSize.Sm => "dock-sm",
DSize.Lg => "dock-lg",
DSize.Xl => "dock-xl",
_ => null
};
}
@@ -0,0 +1,38 @@
@inherits DComponentBase
@* One button of a DDock: ChildContent is the icon and Text the dock-label under it. Renders an <a> when Href is set,
with aria-current="page" while Active. *@
@if (Href is null)
{
<button @attributes="AdditionalAttributes" type="button" class="@CssClass" disabled="@Disabled" @onclick="OnClick">
@ChildContent
@if (Text is not null)
{
<span class="dock-label">@Text</span>
}
</button>
}
else
{
<a @attributes="AdditionalAttributes" href="@(Disabled ? null : Href)" class="@CssClass"
aria-current="@(Active ? "page" : null)" aria-disabled="@(Disabled ? "true" : null)" tabindex="@(Disabled ? "-1" : null)"
@onclick="OnClick">
@ChildContent
@if (Text is not null)
{
<span class="dock-label">@Text</span>
}
</a>
}
@code {
/// <summary>The icon.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Text { get; set; }
[Parameter] public bool Active { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string Href { get; set; }
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
string CssClass => Cx(Active ? "dock-active" : null, Class);
}
+40
View File
@@ -0,0 +1,40 @@
@inherits DComponentBase
@* daisyUI Link: https://daisyui.com/components/link/ . Without Href it renders a <button> styled as a link, so an
OnClick-only link stays keyboard-reachable. *@
@if (Href is null)
{
<button @attributes="AdditionalAttributes" type="button" class="@CssClass" @onclick="OnClick">
@ChildContent
</button>
}
else
{
<a @attributes="AdditionalAttributes" href="@Href" class="@CssClass" @onclick="OnClick">
@ChildContent
</a>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Href { get; set; }
[Parameter] public DColor Color { get; set; }
/// <summary>Underline only on hover (link-hover).</summary>
[Parameter] public bool Hover { get; set; }
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
string CssClass => Cx("link", ColorClass, Hover ? "link-hover" : null, Class);
string ColorClass => Color switch
{
DColor.Neutral => "link-neutral",
DColor.Primary => "link-primary",
DColor.Secondary => "link-secondary",
DColor.Accent => "link-accent",
DColor.Info => "link-info",
DColor.Success => "link-success",
DColor.Warning => "link-warning",
DColor.Error => "link-error",
_ => null
};
}
@@ -0,0 +1,55 @@
@inherits DComponentBase
@* daisyUI Megamenu: https://daisyui.com/components/megamenu/ . Built on the native popover API, as daisyUI specifies:
each DMegamenuItem renders a button and the popover it opens (per-instance ids), so opening, light dismiss and
Escape are the browser's. The root is a popover too: with Responsive it turns, below sm, into a vertical menu that
the toggle button opens. daisyUI supports at most 10 items. text-inherit undoes the browser's popover CanvasText
colour, which daisyUI resets for dropdowns but not here. *@
@if (Responsive || Direction == DDirection.Vertical)
{
<button type="button" class="@Cx("btn", Direction == DDirection.Vertical ? null : "sm:hidden", ToggleClass)" popovertarget="@uid">
@ToggleText
</button>
}
<div @attributes="AdditionalAttributes" id="@uid" popover class="@CssClass">
<span class="megamenu-active"></span>
@ChildContent
</div>
@code {
/// <summary>The DMegamenuItem children.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DMegamenuPanelWidth PanelWidth { get; set; }
[Parameter] public DSize Size { get; set; }
/// <summary>
/// Vertical (megamenu-vertical) hides the menu until the toggle button opens it, at every width. With Horizontal
/// that happens only below sm when Responsive is set.
/// </summary>
[Parameter] public DDirection Direction { get; set; }
/// <summary>Below sm, hide the menu and show the toggle button that opens it vertically (max-sm:megamenu-vertical).</summary>
[Parameter] public bool Responsive { get; set; } = true;
[Parameter] public string ToggleText { get; set; } = "Menu";
[Parameter] public string ToggleClass { get; set; }
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
string CssClass => Cx("megamenu text-inherit", DirectionClass, PanelWidthClass, SizeClass, Class);
string DirectionClass => Direction == DDirection.Vertical ? "megamenu-vertical" : Responsive ? "max-sm:megamenu-vertical" : null;
string PanelWidthClass => PanelWidth switch
{
DMegamenuPanelWidth.Wide => "megamenu-wide",
DMegamenuPanelWidth.Full => "megamenu-full",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "megamenu-xs",
DSize.Sm => "megamenu-sm",
DSize.Lg => "megamenu-lg",
DSize.Xl => "megamenu-xl",
_ => null
};
}
@@ -0,0 +1,12 @@
namespace decePubClient.Components.Daisy.Navigation;
/// <summary>
/// How wide each item's popover is: Default sizes it to its content next to its button, Wide spans the megamenu
/// (megamenu-wide) and Full spans the viewport (megamenu-full).
/// </summary>
public enum DMegamenuPanelWidth
{
Default,
Wide,
Full
}
@@ -0,0 +1,30 @@
@inherits DComponentBase
@* One item of a DMegamenu: its button and the popover it opens, rendered as siblings because daisyUI pairs them by
position (nth-of-type) among the megamenu's direct children. class and other attributes go on the popover. *@
<button type="button" class="@Cx(HideArrow ? "after:content-none" : null, ButtonClass)" popovertarget="@uid">
@if (Header is null)
{
@Text
}
else
{
@Header
}
</button>
<div @attributes="AdditionalAttributes" id="@uid" popover class="@Cx("text-inherit", Class)">
@ChildContent
</div>
@code {
/// <summary>The button's text; Header replaces it with markup.</summary>
[Parameter] public string Text { get; set; }
[Parameter] public RenderFragment Header { get; set; }
/// <summary>The popover's content: a DMenu or anything else.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Drop the arrow daisyUI draws after the button's text (after:content-none).</summary>
[Parameter] public bool HideArrow { get; set; }
[Parameter] public string ButtonClass { get; set; }
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
}
+27
View File
@@ -0,0 +1,27 @@
@inherits DComponentBase
@* daisyUI Menu: https://daisyui.com/components/menu/ . DMenuItem, DMenuTitle and DSubmenu render their own <li>; an
empty <li></li> is a divider. Vertical is daisyUI's default layout and adds no class. *@
<ul @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</ul>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DDirection Direction { get; set; } = DDirection.Vertical;
[Parameter] public DSize Size { get; set; }
/// <summary>Show one submenu level at a time (menu-paged); an open submenu's summary becomes its back button.</summary>
[Parameter] public bool Paged { get; set; }
string CssClass => Cx("menu", Direction == DDirection.Horizontal ? "menu-horizontal" : null, SizeClass,
Paged ? "menu-paged" : null, Class);
string SizeClass => Size switch
{
DSize.Xs => "menu-xs",
DSize.Sm => "menu-sm",
DSize.Lg => "menu-lg",
DSize.Xl => "menu-xl",
_ => null
};
}
@@ -0,0 +1,29 @@
@inherits DComponentBase
@* One item of a DMenu: an <li> holding an <a> when Href is set, otherwise a <button>. class and other attributes go
on the <a>/<button>, which also carries menu-active; menu-disabled goes on the <li>, as daisyUI expects. *@
<li class="@(Disabled ? "menu-disabled" : null)">
@if (Href is null)
{
<button @attributes="AdditionalAttributes" type="button" class="@CssClass" disabled="@Disabled" @onclick="OnClick">
@ChildContent
</button>
}
else
{
<a @attributes="AdditionalAttributes" href="@(Disabled ? null : Href)" class="@CssClass"
aria-disabled="@(Disabled ? "true" : null)" tabindex="@(Disabled ? "-1" : null)" @onclick="OnClick">
@ChildContent
</a>
}
</li>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Href { get; set; }
[Parameter] public bool Active { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
string CssClass => Cx(Active ? "menu-active" : null, Class);
}
@@ -0,0 +1,10 @@
@inherits DComponentBase
@* A title row of a DMenu: <li class="menu-title">. *@
<li @attributes="AdditionalAttributes" class="@Cx("menu-title", Class)">
@ChildContent
</li>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
+29
View File
@@ -0,0 +1,29 @@
@inherits DComponentBase
@* daisyUI Navbar: https://daisyui.com/components/navbar/ . Start, Center and End each render their navbar-* part only
when set; ChildContent goes after them, for content that doesn't fit the three slots. *@
<div @attributes="AdditionalAttributes" class="@Cx("navbar", Class)">
@if (Start is not null)
{
<div class="@Cx("navbar-start", StartClass)">@Start</div>
}
@if (Center is not null)
{
<div class="@Cx("navbar-center", CenterClass)">@Center</div>
}
@if (End is not null)
{
<div class="@Cx("navbar-end", EndClass)">@End</div>
}
@ChildContent
</div>
@code {
[Parameter] public RenderFragment Start { get; set; }
[Parameter] public RenderFragment Center { get; set; }
[Parameter] public RenderFragment End { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string StartClass { get; set; }
[Parameter] public string CenterClass { get; set; }
[Parameter] public string EndClass { get; set; }
}
@@ -0,0 +1,99 @@
@inherits DComponentBase
@* daisyUI Pagination: https://daisyui.com/components/pagination/ . A join of btn join-items; the page is 1-based
(@bind-Current) and the current one is btn-active with aria-current="page". Beyond Siblings pages on each side of
the current one, runs of pages collapse into a disabled ellipsis; the first and last pages always show. *@
<div @attributes="AdditionalAttributes" class="@Cx("join", Direction == DDirection.Vertical ? "join-vertical" : null, Class)">
@if (ShowPreviousNext)
{
<button type="button" class="@ItemClass(false)" disabled="@(Current <= 1)" aria-label="@PreviousAriaLabel"
@onclick="() => Go(Current - 1)">
@PreviousText
</button>
}
@foreach (var number in Pages())
{
if (number == 0)
{
<button type="button" class="@Cx(ItemClass(false), "btn-disabled")" disabled aria-hidden="true">@EllipsisText</button>
}
else
{
var target = number;
<button type="button" class="@ItemClass(number == Current)" aria-current="@(number == Current ? "page" : null)"
@onclick="() => Go(target)">
@number
</button>
}
}
@if (ShowPreviousNext)
{
<button type="button" class="@ItemClass(false)" disabled="@(Current >= Count)" aria-label="@NextAriaLabel"
@onclick="() => Go(Current + 1)">
@NextText
</button>
}
</div>
@code {
/// <summary>The number of pages.</summary>
[Parameter] public int Count { get; set; }
/// <summary>The current page, from 1 to Count.</summary>
[Parameter] public int Current { get; set; } = 1;
[Parameter] public EventCallback<int> CurrentChanged { get; set; }
/// <summary>How many pages to show on each side of the current one before collapsing the rest into "…".</summary>
[Parameter] public int Siblings { get; set; } = 1;
[Parameter] public DSize Size { get; set; }
[Parameter] public DDirection Direction { get; set; }
[Parameter] public bool ShowPreviousNext { get; set; } = true;
[Parameter] public string PreviousText { get; set; } = "«";
[Parameter] public string NextText { get; set; } = "»";
[Parameter] public string PreviousAriaLabel { get; set; } = "Previous page";
[Parameter] public string NextAriaLabel { get; set; } = "Next page";
[Parameter] public string EllipsisText { get; set; } = "…";
string ItemClass(bool active) => Cx("join-item btn", SizeClass, active ? "btn-active" : null);
string SizeClass => Size switch
{
DSize.Xs => "btn-xs",
DSize.Sm => "btn-sm",
DSize.Lg => "btn-lg",
DSize.Xl => "btn-xl",
_ => null
};
// The pages to show in order, with 0 standing for an ellipsis. A gap of a single page shows that page instead.
IEnumerable<int> Pages()
{
if (Count <= 0)
yield break;
var current = Math.Clamp(Current, 1, Count);
var siblings = Math.Clamp(Siblings, 0, Count);
var from = Math.Max(1, current - siblings);
var to = Math.Min(Count, current + siblings);
if (from > 1)
{
yield return 1;
if (from > 2)
yield return from == 3 ? 2 : 0;
}
for (var page = from; page <= to; page++)
yield return page;
if (to < Count)
{
if (to < Count - 1)
yield return to == Count - 2 ? Count - 1 : 0;
yield return Count;
}
}
async Task Go(int page)
{
page = Math.Clamp(page, 1, Math.Max(Count, 1));
if (page == Current)
return;
Current = page;
await CurrentChanged.InvokeAsync(page);
}
}
+32
View File
@@ -0,0 +1,32 @@
@inherits DComponentBase
@* One step of a DSteps: <li class="step">. A Color marks it (usually the completed steps); Marker replaces the number
in its circle (data-content) and Icon replaces the circle's content with a step-icon. *@
<li @attributes="AdditionalAttributes" class="@Cx("step", ColorClass, Class)" data-content="@Marker">
@if (Icon is not null)
{
<span class="step-icon">@Icon</span>
}
@ChildContent
</li>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DColor Color { get; set; }
/// <summary>Text shown in the step's circle instead of its number, e.g. "✓" or "?" (data-content).</summary>
[Parameter] public string Marker { get; set; }
[Parameter] public RenderFragment Icon { get; set; }
string ColorClass => Color switch
{
DColor.Neutral => "step-neutral",
DColor.Primary => "step-primary",
DColor.Secondary => "step-secondary",
DColor.Accent => "step-accent",
DColor.Info => "step-info",
DColor.Success => "step-success",
DColor.Warning => "step-warning",
DColor.Error => "step-error",
_ => null
};
}
+12
View File
@@ -0,0 +1,12 @@
@inherits DComponentBase
@* daisyUI Steps: https://daisyui.com/components/steps/ . Holds DStep children; horizontal is daisyUI's default and adds
no class (for a responsive layout write class="steps-vertical lg:steps-horizontal" yourself). *@
<ul @attributes="AdditionalAttributes" class="@Cx("steps", Direction == DDirection.Vertical ? "steps-vertical" : null, Class)">
@ChildContent
</ul>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DDirection Direction { get; set; }
}
@@ -0,0 +1,46 @@
@inherits DComponentBase
@* A collapsible submenu of a DMenu: <li><details><summary>. The open state lives in C# (@bind-IsOpen): <details>
renders open from IsOpen, and the summary's click toggles IsOpen with the native toggle prevented, so the browser
and Blazor never fight over it. Escape closes the innermost open one (keydown stops bubbling while it is open). *@
<li @attributes="AdditionalAttributes" class="@Cx(Disabled ? "menu-disabled" : null, Class)">
<details open="@IsOpen" @onkeydown="OnKeyDown" @onkeydown:stopPropagation="IsOpen">
<summary class="@HeaderClass" aria-label="@(IsOpen ? BackText : null)" @onclick="Toggle" @onclick:preventDefault="true">
@Header
</summary>
<ul class="@ContentClass">
@ChildContent
</ul>
</details>
</li>
@code {
[Parameter] public RenderFragment Header { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
[Parameter] public bool Disabled { get; set; }
/// <summary>
/// In a paged menu (DMenu Paged) the open summary turns into a back button reading "Back"; this replaces that text.
/// It is set as the summary's aria-label while open, which is where daisyUI reads it from.
/// </summary>
[Parameter] public string BackText { get; set; }
[Parameter] public string HeaderClass { get; set; }
[Parameter] public string ContentClass { get; set; }
async Task Toggle()
{
if (Disabled)
return;
IsOpen = !IsOpen;
await IsOpenChanged.InvokeAsync(IsOpen);
}
async Task OnKeyDown(KeyboardEventArgs e)
{
if (e.Key != "Escape" || !IsOpen)
return;
IsOpen = false;
await IsOpenChanged.InvokeAsync(false);
}
}
+21
View File
@@ -0,0 +1,21 @@
@implements IDisposable
@* One tab of a DTabs: renders nothing itself; DTabs renders its Header as the tab button and its ChildContent as
the panel while it is active. *@
@code {
[CascadingParameter] DTabs Parent { get; set; }
[Parameter] public RenderFragment Header { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string Class { get; set; }
protected override void OnInitialized()
{
if (Parent is null)
throw new InvalidOperationException($"{nameof(DTab)} must be inside a {nameof(DTabs)}.");
Parent.Add(this);
}
public void Dispose() => Parent?.Remove(this);
}
+76
View File
@@ -0,0 +1,76 @@
@inherits DComponentBase
@* daisyUI Tab: https://daisyui.com/components/tab/ . Each DTab child registers itself through the cascading value;
DTabs renders the tab buttons and the active tab's content. The active tab is an index (@bind-Active). *@
<CascadingValue Value="this" IsFixed="true">
@ChildContent
</CascadingValue>
<div @attributes="AdditionalAttributes" role="tablist" class="@CssClass">
@for (var i = 0; i < tabs.Count; i++)
{
var index = i;
var tab = tabs[i];
<button type="button" role="tab" aria-selected="@(index == Active ? "true" : "false")" disabled="@tab.Disabled"
class="@Cx("tab", index == Active ? "tab-active" : null, tab.Disabled ? "tab-disabled" : null, tab.Class)"
@onclick="() => Select(index)">
@tab.Header
</button>
}
</div>
@if (Active >= 0 && Active < tabs.Count)
{
<div role="tabpanel" class="@ContentClass">
@tabs[Active].ChildContent
</div>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public int Active { get; set; }
[Parameter] public EventCallback<int> ActiveChanged { get; set; }
[Parameter] public DTabsVariant Variant { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Bottom { get; set; }
[Parameter] public string ContentClass { get; set; } = "pt-4";
readonly List<DTab> tabs = new();
string CssClass => Cx("tabs", VariantClass, SizeClass, Bottom ? "tabs-bottom" : null, Class);
string VariantClass => Variant switch
{
DTabsVariant.Box => "tabs-box",
DTabsVariant.Border => "tabs-border",
DTabsVariant.Lift => "tabs-lift",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "tabs-xs",
DSize.Sm => "tabs-sm",
DSize.Lg => "tabs-lg",
DSize.Xl => "tabs-xl",
_ => null
};
internal void Add(DTab tab)
{
tabs.Add(tab);
StateHasChanged();
}
internal void Remove(DTab tab)
{
tabs.Remove(tab);
StateHasChanged();
}
async Task Select(int index)
{
Active = index;
await ActiveChanged.InvokeAsync(index);
}
}
@@ -0,0 +1,9 @@
namespace decePubClient.Components.Daisy.Navigation;
public enum DTabsVariant
{
Default,
Box,
Border,
Lift
}