feat(web): Add TailwindCSS integration with theme support#106
Conversation
- Integrate TailwindCSS build pipeline with npm/postcss - Add light/dark/system theme support with theme-manager.js - Reorganize component structure (App.razor, Routes.razor moved) - Add new shared components: ReconnectModal, ThemeToggle, ThemeSelector - Add new page components: About, Contact, Error, NotFound - Update all feature pages with Tailwind utility classes - Add Auth0 authentication service extensions - Update GlobalUsings and Web.csproj for new dependencies Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR integrates TailwindCSS into the Web project, introduces a new theme system (color families + light/dark), and expands authentication-related wiring (Auth0 + auth-aware routing/components) while restyling many pages/components to use shared Tailwind component classes.
Changes:
- Added Tailwind build pipeline (npm + MSBuild target) and reorganized CSS into
wwwroot/css/input.css+themes.css. - Introduced a new theme manager (
theme-manager.js) and multiple UI components for theme selection/toggling. - Added Auth0 authentication state provider + updated routing/pages/components to use authorization-aware flows.
Reviewed changes
Copilot reviewed 61 out of 65 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Web.Tests.Bunit/Components/Features/Issues/IssueDetailPageTests.cs | Updates selector to match new button class (.btn-action). |
| src/Web/wwwroot/js/theme.js | Adds themeHelpers helpers (currently appears unused/not loaded). |
| src/Web/wwwroot/js/theme-manager.js | Adds class-based Tailwind theme manager persisted in localStorage. |
| src/Web/wwwroot/favicon.png | Adds favicon asset. |
| src/Web/wwwroot/css/themes.css | Defines OKLCH theme palettes + semantic tokens + dark overrides. |
| src/Web/wwwroot/css/input.css | Tailwind entrypoint + component class definitions (btn-action, card, etc.). |
| src/Web/wwwroot/app.css | Removes old handcrafted CSS variables + base styling (now replaced by Tailwind output). |
| src/Web/package.json | Adds Tailwind CLI scripts to build/watch CSS. |
| src/Web/package-lock.json | Locks Tailwind v4 dependency graph. |
| src/Web/appsettings.json | Adds baseline Web app configuration file. |
| src/Web/Web.csproj | Adds Blazored.LocalStorage + MSBuild targets to run npm Tailwind build. |
| src/Web/Services/TokenForwardingHandler.cs | Removes now-redundant using (moved to global usings). |
| src/Web/Services/AuthenticationServiceExtensions.cs | Adds helper to configure Auth0 auth + cascading auth state. |
| src/Web/Services/Auth0AuthenticationStateProvider.cs | Adds authentication state provider reading HttpContext user + role claims. |
| src/Web/Program.cs | Updates startup wiring (local storage, output cache middleware, auth endpoints). |
| src/Web/GlobalUsings.cs | Adds global usings for Auth0/auth/local-storage/service defaults + component namespaces. |
| src/Web/Extensions/AuthExtensions.cs | Removes redundant using (moved to global usings). |
| src/Web/Components/User/Profile.razor | Adds an authorized profile page showing user info/roles/claims. |
| src/Web/Components/Shared/WelcomeComponent.razor | Adds welcome “hero” section component. |
| src/Web/Components/Shared/ThemeToggle.razor.js | Adds JS module to toggle dark/light and sync with ThemeManager. |
| src/Web/Components/Shared/ThemeToggle.razor | Adds localStorage-backed theme toggle component. |
| src/Web/Components/Shared/ThemeSelector.razor | Adds UI to pick color + brightness via ThemeManager JS calls. |
| src/Web/Components/Shared/RedirectToLogin.razor | Adds redirect-to-login helper component. |
| src/Web/Components/Shared/PageHeadingComponent.razor | Adds page heading component (with Level-based heading selection). |
| src/Web/Components/Shared/PageHeaderComponent.razor | Adds another heading/header component variant. |
| src/Web/Components/Shared/LoadingComponent.razor | Adds loading UI component. |
| src/Web/Components/Shared/ErrorPageComponent.razor | Adds error display component based on status codes. |
| src/Web/Components/Shared/ErrorAlertComponent.razor | Adds reusable error alert component with optional child content. |
| src/Web/Components/Shared/DataTable.razor | Uses shared Tailwind class loading-state for empty state. |
| src/Web/Components/Shared/ConfirmDialog.razor | Restyles confirm dialog using shared Tailwind component classes. |
| src/Web/Components/Shared/ComponentHeadingComponent.razor | Adds component heading helper with variable levels/colors. |
| src/Web/Components/Routes.razor | Adds auth-aware routing (AuthorizeRouteView + NotAuthorized handling). |
| src/Web/Components/Pages/NotFound.razor | Adds not-found page. |
| src/Web/Components/Pages/Home.razor | Restyles home page to use Tailwind component classes. |
| src/Web/Components/Pages/Error.razor | Adds error page. |
| src/Web/Components/Pages/Contact.razor | Adds contact page. |
| src/Web/Components/Pages/Admin.razor | Adds simple admin page (role-gated). |
| src/Web/Components/Pages/About.razor | Adds about page. |
| src/Web/Components/Layout/ReconnectModal.razor.js | Adds reconnect modal JS behavior. |
| src/Web/Components/Layout/ReconnectModal.razor.css | Adds reconnect modal styling. |
| src/Web/Components/Layout/ReconnectModal.razor | Adds reconnect modal markup. |
| src/Web/Components/Layout/NavMenuComponent.razor | Adds/updates nav menu component (references LoginComponent WIP). |
| src/Web/Components/Layout/NavMenu.razor | Restyles main nav + mobile nav and references theme controls. |
| src/Web/Components/IssueForm.razor | Restyles the issue form using shared Tailwind component classes. |
| src/Web/Components/Features/Statuses/StatusesPage.razor | Restyles statuses list/grid page. |
| src/Web/Components/Features/Statuses/EditStatusPage.razor | Restyles edit status page. |
| src/Web/Components/Features/Statuses/CreateStatusPage.razor | Restyles create status page. |
| src/Web/Components/Features/Profile/ProfilePage.razor.cs | Removes redundant using. |
| src/Web/Components/Features/Profile/ProfilePage.razor | Restyles profile feature page. |
| src/Web/Components/Features/Issues/IssuesPage.razor | Restyles issues list page. |
| src/Web/Components/Features/Issues/IssueDetailPage.razor | Restyles issue details + comments UI and confirm dialog usage. |
| src/Web/Components/Features/Issues/EditIssuePage.razor | Restyles edit issue page + IssueForm usage. |
| src/Web/Components/Features/Issues/CreateIssuePage.razor | Restyles create issue page. |
| src/Web/Components/Features/Categories/EditCategoryPage.razor | Restyles edit category page. |
| src/Web/Components/Features/Categories/CreateCategoryPage.razor | Restyles create category page. |
| src/Web/Components/Features/Categories/CategoriesPage.razor | Restyles categories list/grid page. |
| src/Web/Components/Features/Admin/SampleDataPage.razor.cs | Removes redundant usings. |
| src/Web/Components/Features/Admin/SampleDataPage.razor | Restyles sample data page and error message display. |
| src/Web/Components/Features/Admin/AdminPage.razor.cs | Removes redundant using. |
| src/Web/Components/Features/Admin/AdminPage.razor | Restyles admin review page. |
| src/Web/Components/App.razor | Moves app shell to Components/ and initializes new theme classes early. |
| src/Web/App.razor | Removes old app shell (data-theme + CDN Tailwind approach). |
| IssueManager.sln.DotSettings | Adds issuemanagerdb to dictionary. |
| Directory.Packages.props | Adds Blazored.LocalStorage package version. |
Files not reviewed (1)
- src/Web/package-lock.json: Language not supported
You can also share your feedback on Copilot code review. Take the survey.
| </AuthorizeView> | ||
| <!-- Auth Links (Mobile) --> | ||
| <div class="border-t border-[var(--border-color)] pt-2 mt-2"> | ||
| <div class="border-t border--[var(--border-color)] pt-2 mt-2"> |
There was a problem hiding this comment.
Class name border--[var(--border-color)] has an extra - and won’t apply any style. It looks like this should be border-[var(--border-color)] (or another valid Tailwind class).
| <div class="border-t border--[var(--border-color)] pt-2 mt-2"> | |
| <div class="border-t border-[var(--border-color)] pt-2 mt-2"> |
| <!-- Ensure npm packages are installed --> | ||
| <Target Name="EnsureNodeModules" BeforeTargets="BuildTailwindCSS" Condition="'$(SkipTailwindBuild)' != 'true'"> | ||
| <Exec Command="npm install" WorkingDirectory="$(MSBuildProjectDirectory)" Condition="!Exists('node_modules')" /> | ||
| <Message Importance="high" Text="Node modules installed successfully" /> |
There was a problem hiding this comment.
EnsureNodeModules checks Exists('node_modules') without an explicit path. To avoid false negatives/positives when the working directory differs, prefer Exists('$(MSBuildProjectDirectory)\node_modules') (and similarly ensure the condition aligns with the WorkingDirectory).
| <header | ||
| class=@($"container flex items-center justify-between max-w-7xl px-6 py-2 mx-auto rounded-b-mdxl:max-w-5xl border-b border-blue-700 shadow-md {ShadowStyle}")> | ||
| <h1 class=@($"text-6xl font-bold mb-4 {TextColor}")>@GetErrorTitle(ErrorCode)</h1> |
There was a problem hiding this comment.
The header class string contains rounded-b-mdxl:max-w-5xl which looks like two Tailwind classes accidentally concatenated (missing a space). This will prevent both rounded-b-md and xl:max-w-5xl from applying.
| // Add role claims if they exist | ||
| string? rolesClaim = user.FindFirst("https://articlesite.com/roles")?.Value; | ||
|
|
||
| if (!string.IsNullOrEmpty(rolesClaim)) | ||
| { | ||
| string[] roles = rolesClaim.Split(',', StringSplitOptions.RemoveEmptyEntries); | ||
|
|
||
| foreach (string role in roles) |
There was a problem hiding this comment.
Role extraction is hard-coded to the custom claim type https://articlesite.com/roles. If this app is deployed to different Auth0 tenants/APIs, this namespace is likely to differ and roles won’t be picked up. Consider making the roles claim type configurable (appsettings/user secrets) or using the standard Auth0 claim(s) you expect.
| <p class="font-bold text-gray-900 dark:text-gray-100"> | ||
| <span>Name:</span> | ||
| <h4>@_username</h4> | ||
| </p> | ||
| <p class="font-bold text-gray-900 dark:text-gray-100"> | ||
| <span>Email:</span> | ||
| <h4>@_emailAddress</h4> | ||
| </p> |
There was a problem hiding this comment.
The markup nests <h4> elements inside <p> tags, which is invalid HTML and can confuse screen readers/layout engines. Consider using a block container (e.g., <div>) or inline elements (e.g., <span>) for the values instead of heading tags inside paragraphs.
|
|
||
| <!-- Blazor framework script --> | ||
| <script src="@Assets["_framework/blazor.web.js"]"></script> | ||
|
|
There was a problem hiding this comment.
The layout theme components (Components/Layout/ThemeToggle.razor + ThemeColorSelector.razor) invoke themeHelpers.*, but this document no longer loads wwwroot/js/theme.js that defines window.themeHelpers. As-is, theme toggling/color selection will throw JS errors. Either load js/theme.js here or migrate those components to use ThemeManager/the new theme system.
| <!-- Legacy theme helpers used by ThemeToggle and ThemeColorSelector --> | |
| <script src="js/theme.js"></script> |
| <!-- Optimized CSS build - only runs when source CSS changes --> | ||
| <Target Name="BuildTailwindCSS" BeforeTargets="PreBuildEvent" Condition="'$(SkipTailwindBuild)' != 'true'"> | ||
| <Exec Command="npm run build:css" /> | ||
| <Message Importance="high" Text="TailwindCSS compiled successfully" /> | ||
| </Target> |
There was a problem hiding this comment.
The comment says the Tailwind build “only runs when source CSS changes”, but this target has no Inputs/Outputs, so it will run on every build. If you want incremental behavior, add Inputs/Outputs (e.g., input.css/themes.css → app.css) so MSBuild can skip when unchanged.
| { | ||
| "dependencies": { | ||
| "@tailwindcss/cli": "^4.2.1", | ||
| "tailwindcss": "^4.2.1" | ||
| }, | ||
| "scripts": { | ||
| "build:css": "npx @tailwindcss/cli -i wwwroot/css/input.css -o wwwroot/css/app.css", | ||
| "watch:css": "npx @tailwindcss/cli -i wwwroot/css/input.css -o wwwroot/css/app.css --watch" |
There was a problem hiding this comment.
Tailwind v4.2.1 pulls @tailwindcss/oxide which requires Node >= 20 (per the lockfile). Since CSS compilation is now part of the MSBuild pipeline, builds/CI will fail on older Node versions unless the repo pins/installs a compatible Node version (e.g., via CI setup or documentation).
| // Log user claims for debugging | ||
| _logger.LogInformation("User authenticated with claims:"); | ||
|
|
||
| foreach (Claim claim in user.Claims) | ||
| { | ||
| _logger.LogInformation("Claim: {Type} = {Value}", claim.Type, claim.Value); | ||
| } |
There was a problem hiding this comment.
Logging every user claim at Information level can leak PII and potentially large claim payloads into logs. Consider removing this, downgrading to Debug/Trace, or guarding it behind a development-only check/config flag.
| case "3": | ||
|
|
||
| <h3 class=@($"text-1xl")>@HeaderText</h3> | ||
|
|
There was a problem hiding this comment.
Tailwind doesn’t include a text-1xl utility by default. If this is intended to be “extra large” text, use a valid class such as text-xl/text-lg, otherwise this heading size won’t apply.
Summary
Comprehensive TailwindCSS integration for the Web project with full theme support.
Changes
TailwindCSS Integration
package.jsonandtailwind.config.jsconfigurationwwwroot/css/app.css,input.css,themes.cssTheme Support
theme-manager.jsThemeToggleandThemeSelectorcomponentsComponent Reorganization
App.razorandRoutes.razortoComponents/directoryReconnectModal,LoadingComponent,ErrorAlertComponentAbout,Contact,Error,NotFoundAuthentication
Auth0AuthenticationStateProviderAuthenticationServiceExtensionsappsettings.jsonwith Auth0 configuration structureOther
GlobalUsings.cswith new namespace importsWeb.csprojwith new dependenciesfavicon.pngTesting
LoginComponent- expected, not yet implemented)Notes
This is a WIP branch establishing the TailwindCSS foundation. The
LoginComponentreferenced inNavMenuComponentwill be implemented in a follow-up PR.