Skip to content

Admin shell and component set #2434

Closed

Description

@pournasserian

Goal

Stand up the admin as a Blazor Web App in FluentCMS.Admin (ADR-0006) with the styling pipeline, the theme and the owned component set from ADR-0011, so that the first feature screens start from working, tested primitives instead of building them. The first slice is the one-week spike ADR-0011 leaves open, and its outcome goes into the ADR before the ADR is accepted. No feature screen is part of this issue.

First slice: the spike (one week)

  • Build Dialog, DropdownMenu and Command both ways: on our own headless primitives (native <dialog>, the Popover API and the interop module below) and on Blazor Blueprint's headless layer (Apache 2.0).
  • Compare the two on the points ADR-0011 names: keyboard and screen-reader behaviour, RTL, behaviour over an interactive-server circuit including reconnect, dependency size, and the project's maintenance activity.
  • Write the outcome into the "Open until the spike" section of ADR-0011 in its own pull request and set the status to accepted (ADR-0001). The rest of this issue continues only after that pull request is merged.
  • Delete the losing variant. Either way, no styled component comes from a third-party library.

Shell

  • Blazor Web App in FluentCMS.Admin: routes, root component and layout, interactive server render mode for application pages, static server rendering available for stateless pages. Composed by Host; Admin keeps referencing only Core (ADR-0004).
  • Root element carries the dir attribute and the theme class. The home route stays a placeholder until F-DSK (F-DSK: Review desk as the home screen #2422) makes the desk the landing page.
  • One small JavaScript interop module for what the platform does not cover: focus trapping, roving tab index and global keyboard shortcuts. It is the only JavaScript in the admin.
  • Default reconnect handling, so the spike and the component tests can exercise behaviour over a lost circuit (ADR-0006).

Styling and theme

  • Tailwind CSS v4 through the standalone CLI: an MSBuild target in the Admin project downloads the pinned binary once per machine into a cache, verifies its checksum, and compiles the stylesheet before each build. An MSBuild property pointing at a pre-downloaded binary serves offline and CI builds. No Node, no npm, no JavaScript Tailwind plugins.
  • CI caches the binary keyed on its pinned version.
  • shadcn tokens verbatim as CSS variables (background, foreground, primary, muted, border, ring, radius and the rest), light and dark themes switched by a class on the root element and defaulting to the system preference.
  • A C# port of tailwind-merge for class merging and a small variants helper in the CVA style, so shadcn examples translate one-to-one into Razor.
  • Lucide icons as inline SVG components generated from the icon set.

Component set

  • Components live in FluentCMS.Admin/Components/Ui/, no separate project, no package. Each is self-contained: Razor markup with Tailwind classes in the shadcn style, parameters limited to what the screens use, no base-class hierarchy.
  • The initial set from ADR-0011: Button, Input, Textarea, Select, Checkbox, Switch, RadioGroup, Badge, Card, Separator, Tabs, Dialog, Sheet, DropdownMenu, Popover, Tooltip, Command, Toast, Table (QuickGrid with the shadcn table classes), Kbd, FormField (label, description, error, and a slot for the active rule hints FRM-03 needs; the hints themselves come with F-FRM), and DiffView for the desk. DiffView renders the structured diff model from Core, which F-CHG (F-CHG: Change sets with diff, atomic publish and one-click revert #2418) defines, so it is the last component and may land alongside that slice.
  • Localization and RTL from the first component: every user-facing string goes through IStringLocalizer resources; layout uses Tailwind's logical utilities (start and end, not left and right); the root dir attribute flips the direction.
  • bUnit tests per component for rendering and keyboard behaviour, in tests/FluentCMS.Tests.
  • The /ui showcase page rendering every component with its keyboard hints, in light and dark themes. Reachable only in the Development environment until F-USR (F-USR: Users, sign-in and the flat administrator role #2428) provides sign-in, then restricted to signed-in users. It is the living documentation for contributors and the visual regression target.
  • Document the Tailwind standalone step and the offline path under "Building and testing" in CONTRIBUTING.md.

Out of scope

Depends on

Acceptance

From the consequences of ADR-0011 and UX-04:

  • The spike outcome is recorded in ADR-0011 and the ADR is accepted. Beyond what the spike chose for the headless layer, FluentCMS.Admin has no runtime dependency on a UI library.
  • dotnet build compiles the stylesheet on a machine without Node; a build with the pre-downloaded binary succeeds offline; CI downloads the binary once and serves it from cache afterwards.
  • /ui renders every component of the initial set in both themes with its keyboard hints, is usable at phone width, passes automated WCAG 2.1 AA checks (UX-04), and renders correctly with the root dir attribute set to rtl.
  • Every component has bUnit tests for rendering and keyboard behaviour. Focus trapping in Dialog, roving focus in DropdownMenu and Command, and global shortcuts work over an interactive-server circuit and still work after a reconnect, because the interop module keeps focus, scroll and key capture on the client.
  • No user-facing string in the shell or the components is hard-coded; all come from IStringLocalizer resources.
  • A shadcn documentation example for a component in the set translates into Razor with the same class names and the same keyboard behaviour.

Activity

  1. pournasserian commented on Oct 17, 2026

    @pournasserian
    MemberAuthor

    Spike result (first slice): write the headless primitives in this repository, native HTML first, with one interop module. Pull request #2444 carries the shell, the Tailwind pipeline, the winning Dialog, DropdownMenu and Command, the spike record (docs/spikes/2026-10-admin-primitives.md) and ADR-0011 set to accepted.

    Why, in short: Blazor Blueprint's headless layer has no Command primitive, so one of the three components would have been ours anyway or a dependency on its styled package. Its overlays keep their open state on the server and need a portal host inside an interactive subtree, which per-page render modes make awkward; the native variant keeps working through a lost circuit and after a pause and resume, and needs no portal host, no scoped services and no per-component interop. Keyboard, screen-reader and RTL behaviour were otherwise equivalent, with the native variant ahead on three details (inert page behind a modal, focus on the first menu item, focus back to the trigger after Escape). The shadcn wrappers around Blueprint were as long as the primitives themselves, the package adds 891 KB of assembly and 212 KB of JavaScript for two components, and the project has shipped three major versions in eight months from one maintainer.

    To see both variants side by side before deciding: commit 4595d28 on the branch (git checkout 4595d282, run the host in Development, open /ui-spike). The final commit deletes the losing variant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:choreRepository, tooling or housekeepingv2FluentCMS v2 (v2 branch)

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions