diff --git a/apps/component-calling/src/App.vue b/apps/component-calling/src/App.vue
index 748a0ce59..6d90fe6f1 100644
--- a/apps/component-calling/src/App.vue
+++ b/apps/component-calling/src/App.vue
@@ -1,9 +1,8 @@
diff --git a/apps/stage-tamagotchi/src/renderer/App.vue b/apps/stage-tamagotchi/src/renderer/App.vue
index d84216faf..609722eb0 100644
--- a/apps/stage-tamagotchi/src/renderer/App.vue
+++ b/apps/stage-tamagotchi/src/renderer/App.vue
@@ -3,7 +3,7 @@ import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models'
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
-import { useDark } from '@vueuse/core'
+import { useTheme } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -13,7 +13,7 @@ import { electronOpenSettings, electronStartTrackMousePosition } from '../shared
import { useElectronEventaContext } from './composables/electron-vueuse'
import { themeColorFromValue, useThemeColor } from './composables/theme-color'
-const dark = useDark({ disableTransition: false })
+const { isDark: dark } = useTheme()
const i18n = useI18n()
const displayModelsStore = useDisplayModelsStore()
const settingsStore = useSettings()
diff --git a/apps/stage-tamagotchi/src/renderer/components/StageIslands/ControlsIsland/index.vue b/apps/stage-tamagotchi/src/renderer/components/StageIslands/ControlsIsland/index.vue
index 77d0da7ef..cde4bf399 100644
--- a/apps/stage-tamagotchi/src/renderer/components/StageIslands/ControlsIsland/index.vue
+++ b/apps/stage-tamagotchi/src/renderer/components/StageIslands/ControlsIsland/index.vue
@@ -1,7 +1,7 @@
diff --git a/apps/stage-web/src/components/Layouts/InteractiveArea.vue b/apps/stage-web/src/components/Layouts/InteractiveArea.vue
index 200f3c93a..02c01481d 100644
--- a/apps/stage-web/src/components/Layouts/InteractiveArea.vue
+++ b/apps/stage-web/src/components/Layouts/InteractiveArea.vue
@@ -7,8 +7,7 @@ import { useChatStore } from '@proj-airi/stage-ui/stores/chat'
import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
-import { BasicTextarea, FieldSelect } from '@proj-airi/ui'
-import { useDark } from '@vueuse/core'
+import { BasicTextarea, FieldSelect, useTheme } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
import { computed, onUnmounted, ref, watch } from 'vue'
@@ -32,7 +31,7 @@ const { messages } = storeToRefs(useChatStore())
const { audioContext } = useAudioContext()
const { t } = useI18n()
-const isDark = useDark({ disableTransition: false })
+const { isDark, toggleDark } = useTheme()
// Legacy whisper pipeline removed; audio pipeline handled at page level
@@ -240,7 +239,7 @@ onUnmounted(() => {
text="lg neutral-500 dark:neutral-400"
flex items-center justify-center rounded-md p-2 outline-none
transition-colors transition-transform active:scale-95
- @click="isDark = !isDark"
+ @click="toggleDark()"
>
diff --git a/apps/stage-web/src/components/Layouts/MobileHeaderLink.vue b/apps/stage-web/src/components/Layouts/MobileHeaderLink.vue
index 5a49aeb0b..709275eeb 100644
--- a/apps/stage-web/src/components/Layouts/MobileHeaderLink.vue
+++ b/apps/stage-web/src/components/Layouts/MobileHeaderLink.vue
@@ -1,11 +1,11 @@
diff --git a/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue b/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue
index efc3eeef7..a1333d7dc 100644
--- a/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue
+++ b/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue
@@ -8,8 +8,8 @@ import { useChatStore } from '@proj-airi/stage-ui/stores/chat'
import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
-import { BasicTextarea } from '@proj-airi/ui'
-import { useDark, useResizeObserver, useScreenSafeArea } from '@vueuse/core'
+import { BasicTextarea, useTheme } from '@proj-airi/ui'
+import { useResizeObserver, useScreenSafeArea } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -21,7 +21,7 @@ import ActionAbout from './InteractiveArea/Actions/About.vue'
import ActionViewControls from './InteractiveArea/Actions/ViewControls.vue'
import ViewControlInputs from './ViewControls/Inputs.vue'
-const isDark = useDark({ disableTransition: false })
+const { isDark, toggleDark } = useTheme()
const hearingDialogOpen = ref(false)
const viewControlsActiveMode = ref<'x' | 'y' | 'z' | 'scale'>('scale')
@@ -163,7 +163,7 @@ onMounted(() => {
-