Skip to content

Tailwind 4 + DaisyUI 5 upgrade + light/dark theme switcher#8

Merged
lebedevilya merged 6 commits into
mainfrom
tailwind-4
May 25, 2026
Merged

Tailwind 4 + DaisyUI 5 upgrade + light/dark theme switcher#8
lebedevilya merged 6 commits into
mainfrom
tailwind-4

Conversation

@lebedevilya

Copy link
Copy Markdown
Owner

Closes the modernization story. Two coupled major bumps (Tailwind 3 to 4, DaisyUI 4 to 5, since DaisyUI 5 requires Tailwind 4) plus a light/dark theme toggle in the sidebar.

Toolchain

Before After
tailwindcss 3 4
daisyui 4 5
Astro Tailwind glue @astrojs/tailwind (deprecated on Astro 5) @tailwindcss/vite
Config location tailwind.config.cjs (JS) src/styles/global.css (@plugin)

Tailwind 4's CSS-first config replaces the JS config file. DaisyUI 5 is configured via @plugin "daisyui" {...} inside the same CSS file.

Theme set narrowed from 'all daisyUI themes' to just lofi and dark; that alone cuts ~70 KB of unused theme CSS from the bundle.

Theme switcher

  • Bootstrap (no FOUC). Inline script in BaseHead runs before first paint. Reads localStorage.theme if set, otherwise prefers-color-scheme. Applies data-theme to <html> immediately.
  • Toggle button. Sun/moon icon button in the sidebar footer next to the social icons. Click calls window.__toggleTheme() (exposed by the bootstrap) which flips between lofi and dark and persists the choice.
  • Icon swap is pure CSS. [[data-theme='lofi']_&]:block / [[data-theme='dark']_&]:block show the correct icon for the active theme.
  • View Transitions interop. <html> is preserved across page swaps so data-theme survives navigation. The astro:after-swap listener only re-evaluates if the user has never explicitly chosen a theme (in case their OS theme flipped mid-session).
  • No-JS fallback. <html data-theme="lofi"> is still hardcoded so the site renders in light mode even with JS disabled.

Verified

  • npm run build -> 8 pages, sitemap, FAQ schema still in /cv
  • npx astro check -> 0 errors

What's likely to need eyeballing

DaisyUI 5 occasionally tweaks component visuals subtly (button padding, badge border-radius, etc). I didn't see anything obvious in the build but a click-through is worth doing once it's live preview.

Toolchain:
- tailwindcss 3 -> 4 (CSS-first config; no more tailwind.config.cjs)
- daisyui 4 -> 5
- @astrojs/tailwind dropped in favour of @tailwindcss/vite (the
  integration is the deprecated path on Astro 5)
- @tailwindcss/typography stays, registered via @plugin in CSS

Theme config (src/styles/global.css):
- Replaces the JS tailwind.config + daisyui plugin block. Tailwind +
  DaisyUI are configured purely in CSS via @import "tailwindcss";
  @plugin "daisyui" {...}
- Restricts themes to lofi + dark (was 'all themes'); cuts ~70 KB of
  unused theme CSS from the bundle
- @custom-variant dark (...) exposes dark: utilities reactive to the
  daisyUI theme (not OS prefers-color-scheme) so any future dark:
  overrides match the user's actual choice

Theme switcher:
- BaseHead now ships an inline bootstrap script that runs before first
  paint: reads localStorage 'theme' (falls back to prefers-color-scheme),
  applies data-theme to <html>. No flash of wrong theme.
- Exposes window.__toggleTheme() globally so a button anywhere on the
  page can flip themes.
- Sidebar footer gets a sun/moon icon button next to the social icons.
  Click flips theme + persists to localStorage. CSS variants
  ([[data-theme='lofi']_&]:block, [[data-theme='dark']_&]:block) decide
  which icon shows, no JS needed for icon swapping.
- BaseLayout keeps data-theme='lofi' as a no-JS fallback; the bootstrap
  script overrides before paint.

astro:after-swap listener re-applies prefers-color-scheme only when the
user has never picked a theme explicitly; otherwise data-theme survives
the page swap since <html> isn't replaced by ClientRouter.

Build + astro check both green.
@netlify

netlify Bot commented May 25, 2026

Copy link
Copy Markdown

Deploy Preview for grand-croquembouche-1d568d ready!

Name Link
🔨 Latest commit 28e2fc2
🔍 Latest deploy log https://app.netlify.com/projects/grand-croquembouche-1d568d/deploys/6a14c93e58e0af00087668cc
😎 Deploy Preview https://deploy-preview-8--grand-croquembouche-1d568d.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41d78e4d2f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/BaseHead.astro
Netlify default is Node 18.20.8, which ships an npm version with bug
npm/cli#4828: optional dependencies sometimes don't resolve cross-platform
when the lockfile was generated on a different OS, leaving the build
unable to find @tailwindcss/oxide-linux-x64-gnu.

Fix:
- .nvmrc: 22 (matches GH Actions CI Node version)
- netlify.toml: NODE_VERSION=22, NPM_CONFIG_INCLUDE_OPTIONAL=true,
  explicit 'npm install --include=optional' before build (instead of
  relying on Netlify's default npm ci, which is strict to the lockfile
  and won't fetch missing platform binaries).
Codex review (P2) caught that the original bootstrap wrote
localStorage.theme during every initial apply(resolve()), even when
the visitor had not chosen a theme. That froze the very first
OS-derived value into storage, defeating the intended auto-follow
behaviour and making the astro:after-swap re-evaluation dead code.

Refactor splits apply (DOM only) from persist (localStorage). Only
the toggle-button click path persists, so visitors who never click
keep following their OS forever, including across OS theme changes.

Also drops the astro:after-swap branch in favour of a
matchMedia('(prefers-color-scheme)').addEventListener('change')
listener that fires whenever the OS theme actually flips. Cleaner
and reacts in real time rather than only on navigation.
Theme toggle:
- New ThemeToggle.astro: fixed top-4 right-4, z-50, size-12 (48px)
  btn-circle with translucent bg-base-100/70 + backdrop-blur + border +
  shadow so it floats clearly over content without dominating
- Mounted in BaseLayout (outside the drawer container) so it appears
  on every page; removed from SideBarFooter
- Bigger icon (size-6, was 24px) plus larger button hit-area

DaisyUI 5 menu fix:
- Sidebar nav items (Home, CV, Travel, Contact) rendered narrower than
  the sidebar after the v5 upgrade because v5 menu items size to
  content by default. Added w-full to the ul, every li, and every a so
  the nav items fill the sidebar width as they did under v4.
…e survives navigation

Bug: with ClientRouter, navigating from one page to another caused the
theme to flash back to lofi even when the user had loaded the site in
dark mode. Reason: every page ships with <html data-theme="lofi"> as
the no-JS fallback, and Astro's View Transitions overwrite live <html>
attributes with the incoming document's attributes during a swap. The
bootstrap script that initially picked dark only runs on first load,
so the live data-theme="dark" got clobbered.

Fix: a single astro:before-swap listener mutates the incoming document
before it's swapped in, so the new page already has the right
data-theme by the time the user sees it. No flash, no visible state
change on click.
@lebedevilya lebedevilya merged commit af77ded into main May 25, 2026
6 checks passed
@lebedevilya lebedevilya deleted the tailwind-4 branch May 25, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant