Skip to content

Conversation

@moshetanzer
Copy link
Contributor

@moshetanzer moshetanzer commented Nov 6, 2025

This is just the first one to see if the team are happy with it - will do rest of buttons if you are 😄 thanks for your work

EDIT

Cant really think of a normal way to do UColorModeSwitch and UColorModeSelect and UColorModeAvatar and UColorModeImage are already using the method used in this implem.

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@github-actions github-actions bot added the v4 #4488 label Nov 6, 2025
<slot name="fallback">
<div class="size-8" />
</slot>
<UButton
Copy link
Contributor

Choose a reason for hiding this comment

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

The component is missing the ClientOnly wrapper and v-if="!colorMode?.forced" check that was present in the original code, which can cause the button to render when the color mode is forced and potentially cause SSR hydration mismatches.

View Details
📝 Patch Details
diff --git a/src/runtime/components/color-mode/ColorModeButton.vue b/src/runtime/components/color-mode/ColorModeButton.vue
index bcb2f8cd..e3cf1e47 100644
--- a/src/runtime/components/color-mode/ColorModeButton.vue
+++ b/src/runtime/components/color-mode/ColorModeButton.vue
@@ -27,9 +27,7 @@ const props = withDefaults(defineProps<ColorModeButtonProps>(), {
   color: 'neutral',
   variant: 'ghost'
 })
-defineSlots<{
-  fallback(props?: {}): any
-}>()
+
 
 const { t } = useLocale()
 const colorMode = useColorMode()
@@ -48,17 +46,23 @@ const isDark = computed({
 </script>
 
 <template>
-  <UButton
-    v-bind="{
-      ...buttonProps,
-      'aria-label': isDark ? t('colorMode.switchToLight') : t('colorMode.switchToDark'),
-      ...$attrs
-    }"
-    @click="isDark = !isDark"
-  >
-    <template #leading>
-      <UIcon class="hidden dark:inline" :name="appConfig.ui.icons.dark" />
-      <UIcon class="inline dark:hidden" :name="appConfig.ui.icons.light" />
+  <ClientOnly v-if="!colorMode?.forced">
+    <UButton
+      v-bind="{
+        ...buttonProps,
+        'aria-label': isDark ? t('colorMode.switchToLight') : t('colorMode.switchToDark'),
+        ...$attrs
+      }"
+      @click="isDark = !isDark"
+    >
+      <template #leading>
+        <UIcon class="hidden dark:inline" :name="appConfig.ui.icons.dark" />
+        <UIcon class="inline dark:hidden" :name="appConfig.ui.icons.light" />
+      </template>
+    </UButton>
+
+    <template #fallback>
+      <div class="size-8" />
     </template>
-  </UButton>
+  </ClientOnly>
 </template>

Analysis

ColorModeButton missing ClientOnly wrapper and forced mode check

What fails: ColorModeButton.vue renders button even when colorMode.forced is true and lacks ClientOnly wrapper for SSR safety, unlike other color mode components

How to reproduce:

  1. Set color mode to forced in Nuxt config: colorMode: { forced: 'dark' }
  2. Use <UColorModeButton /> in template
  3. Button renders but clicking it has no effect (user cannot change forced mode)

Result: Button renders when it shouldn't, inconsistent with ColorModeSelect.vue and ColorModeSwitch.vue behavior

Expected: Button should not render when colorMode.forced is true, per official documentation pattern and consistency with other color mode components that use <ClientOnly v-if="!colorMode?.forced">

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 6, 2025

npm i https://pkg.pr.new/@nuxt/ui@5394

commit: 8aaddd2

@moshetanzer moshetanzer changed the title feat: use css to detect color mode to avoid using <ClientOnly> refactor: use css to detect color mode to avoid using <ClientOnly> Nov 6, 2025
@benjamincanac benjamincanac changed the title refactor: use css to detect color mode to avoid using <ClientOnly> fix(ColorModeButton): use css to detect color mode to avoid using <ClientOnly> Nov 10, 2025
Copy link
Member

@benjamincanac benjamincanac left a comment

Choose a reason for hiding this comment

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

I've checked in the preview and it's rendered super small 🤔

Also, you need to remove the defineSlots which is useless.

Could this be worth applying the same changes in the Vue ColodeModeButton as well?

@moshetanzer
Copy link
Contributor Author

cant imagine what issue size could be. Have removed unused code

Comment on lines +57 to +58
<UIcon :class="ui.leadingIcon({ class: props.ui?.trailingIcon })" class="hidden dark:inline" :name="appConfig.ui.icons.dark" />
<UIcon :class="ui.leadingIcon({ class: props.ui?.trailingIcon })" class="inline dark:hidden" :name="appConfig.ui.icons.light" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<UIcon :class="ui.leadingIcon({ class: props.ui?.trailingIcon })" class="hidden dark:inline" :name="appConfig.ui.icons.dark" />
<UIcon :class="ui.leadingIcon({ class: props.ui?.trailingIcon })" class="inline dark:hidden" :name="appConfig.ui.icons.light" />
<UIcon :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" class="hidden dark:inline" :name="appConfig.ui.icons.dark" />
<UIcon :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" class="inline dark:hidden" :name="appConfig.ui.icons.light" />

Using props.ui?.trailingIcon instead of props.ui?.leadingIcon when styling leading icons, which applies incorrect styling classes.

View Details

Analysis

ColorModeButton uses trailingIcon property for leadingIcon styling

What fails: ColorModeButton.vue uses props.ui?.trailingIcon instead of props.ui?.leadingIcon when styling leading icons, causing incorrect styling classes to be applied

How to reproduce: Pass custom ui.leadingIcon styles to ColorModeButton component - they will be ignored, and any ui.trailingIcon styles will incorrectly be applied to the leading icons instead

Result: Leading icon styles are not applied correctly. Custom leadingIcon styling is ignored while trailingIcon styling incorrectly affects leading icons.

Expected: Leading icons should use props.ui?.leadingIcon property, consistent with all other components in the codebase (Badge.vue, Button.vue, Input.vue, H2.vue, H3.vue, etc.)

Pattern evidence: All 20+ other components use ui.leadingIcon({ class: props.ui?.leadingIcon }) but ColorModeButton uses ui.leadingIcon({ class: props.ui?.trailingIcon })

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

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants