diff --git a/app/composables/useAccentColor.ts b/app/composables/useAccentColor.ts
deleted file mode 100644
index 2762b5aeb1..0000000000
--- a/app/composables/useAccentColor.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { computed } from 'vue'
-import { useLocalStorage } from '@vueuse/core'
-
-export interface AccentColor {
- id: string
- name: string
- value: string
-}
-
-export const ACCENT_COLORS = [
- { id: 'rose', name: 'Rose', value: '#e9aeba' },
- { id: 'amber', name: 'Amber', value: '#fbbf24' },
- { id: 'emerald', name: 'Emerald', value: '#34d399' },
- { id: 'sky', name: 'Sky', value: '#38bdf8' },
- { id: 'violet', name: 'Violet', value: '#a78bfa' },
- { id: 'coral', name: 'Coral', value: '#fb7185' },
-] as const satisfies readonly AccentColor[]
-
-export type ColorId = (typeof ACCENT_COLORS)[number]['id']
-
-function applyColorToDocument(color: string | null) {
- if (color) {
- document.documentElement.style.setProperty('--accent-color', color)
- } else {
- document.documentElement.style.removeProperty('--accent-color')
- }
-}
-
-export function useAccentColor() {
- const accentColorId = useLocalStorage
('app-accent', null)
-
- function setAccentColor(id: ColorId | null) {
- const chosenColor = ACCENT_COLORS.find(color => color.id === id)?.value ?? null
- applyColorToDocument(chosenColor)
- accentColorId.value = id
- }
-
- return {
- accentColorId: computed(() => accentColorId.value),
- setAccentColor,
- }
-}
diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts
index 67328b29e3..5d013fd7dd 100644
--- a/app/composables/useSettings.ts
+++ b/app/composables/useSettings.ts
@@ -1,5 +1,8 @@
import type { RemovableRef } from '@vueuse/core'
import { useLocalStorage } from '@vueuse/core'
+import { ACCENT_COLORS } from '#shared/utils/constants'
+
+type AccentColorId = keyof typeof ACCENT_COLORS
/**
* Application settings stored in localStorage
@@ -9,11 +12,14 @@ export interface AppSettings {
relativeDates: boolean
/** Include @types/* package in install command for packages without built-in types */
includeTypesInInstall: boolean
+ /** Accent color theme */
+ accentColorId: AccentColorId | null
}
const DEFAULT_SETTINGS: AppSettings = {
relativeDates: false,
includeTypesInInstall: true,
+ accentColorId: null,
}
const STORAGE_KEY = 'npmx-settings'
@@ -45,3 +51,32 @@ export function useRelativeDates() {
const { settings } = useSettings()
return computed(() => settings.value.relativeDates)
}
+
+/**
+ * Composable for managing accent color.
+ */
+export function useAccentColor() {
+ const { settings } = useSettings()
+
+ const accentColors = Object.entries(ACCENT_COLORS).map(([id, value]) => ({
+ id: id as AccentColorId,
+ name: id,
+ value,
+ }))
+
+ function setAccentColor(id: AccentColorId | null) {
+ const color = id ? ACCENT_COLORS[id] : null
+ if (color) {
+ document.documentElement.style.setProperty('--accent-color', color)
+ } else {
+ document.documentElement.style.removeProperty('--accent-color')
+ }
+ settings.value.accentColorId = id
+ }
+
+ return {
+ accentColors,
+ selectedAccentColor: computed(() => settings.value.accentColorId),
+ setAccentColor,
+ }
+}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index dd2ec3f5e9..93e1de7717 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,3 +1,7 @@
+import { ACCENT_COLORS } from './shared/utils/constants'
+
+const accentInitScript = `(function(){var s=JSON.parse(localStorage.getItem('npmx-settings'));if(s&&s.accentColorId){document.documentElement.style.setProperty('--accent-color',${JSON.stringify(ACCENT_COLORS)}[s.accentColorId])}})()`
+
export default defineNuxtConfig({
modules: [
function (_, nuxt) {
@@ -48,8 +52,7 @@ export default defineNuxtConfig({
],
script: [
{
- innerHTML: `(function(){var c={rose:'#e9aeba',amber:'#fbbf24',emerald:'#34d399',sky:'#38bdf8',violet:'#a78bfa',coral:'#fb7185'};var s=localStorage.getItem('npmx-accent');document.documentElement.style.setProperty('--accent-color',s&&c[s]||'#666666')})()`,
- type: 'text/javascript',
+ innerHTML: accentInitScript,
},
],
},
diff --git a/shared/utils/constants.ts b/shared/utils/constants.ts
index e4efd422f6..f4ec66d1f1 100644
--- a/shared/utils/constants.ts
+++ b/shared/utils/constants.ts
@@ -16,3 +16,13 @@ export const NPM_MISSING_README_SENTINEL = 'ERROR: No README data found!'
export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.'
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'
export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.'
+
+// Theming
+export const ACCENT_COLORS = {
+ rose: '#e9aeba',
+ amber: '#fbbf24',
+ emerald: '#34d399',
+ sky: '#38bdf8',
+ violet: '#a78bfa',
+ coral: '#fb7185',
+} as const
diff --git a/uno.config.ts b/uno.config.ts
index 2951935485..4322497726 100644
--- a/uno.config.ts
+++ b/uno.config.ts
@@ -49,7 +49,6 @@ export default defineConfig({
accent: {
DEFAULT: 'var(--accent-color, #666666)',
fallback: '#666666',
- muted: '#e5e5e5',
},
// Syntax highlighting colors (inspired by GitHub Dark)
syntax: {
From 952d44d88f1134796f6ce819111ef1f0d8d3b35e Mon Sep 17 00:00:00 2001
From: Rshig <90143161+rshigg@users.noreply.github.com>
Date: Tue, 27 Jan 2026 08:25:31 -0500
Subject: [PATCH 3/9] mock useAccentColor in DateTime tests
---
test/nuxt/components/DateTime.spec.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/test/nuxt/components/DateTime.spec.ts b/test/nuxt/components/DateTime.spec.ts
index 5c5de7e692..6fd61abd01 100644
--- a/test/nuxt/components/DateTime.spec.ts
+++ b/test/nuxt/components/DateTime.spec.ts
@@ -9,6 +9,7 @@ vi.mock('~/composables/useSettings', () => ({
useSettings: () => ({
settings: ref({ relativeDates: mockRelativeDates.value }),
}),
+ useAccentColor: () => ({}),
}))
describe('DateTime', () => {
From 413d1f19ee4aae3c2334f6275c980308f44df7a4 Mon Sep 17 00:00:00 2001
From: Rshig <90143161+rshigg@users.noreply.github.com>
Date: Tue, 27 Jan 2026 08:46:42 -0500
Subject: [PATCH 4/9] remove SearchInput.vue
---
app/components/SearchInput.vue | 60 ----------------------------------
1 file changed, 60 deletions(-)
delete mode 100644 app/components/SearchInput.vue
diff --git a/app/components/SearchInput.vue b/app/components/SearchInput.vue
deleted file mode 100644
index 6ec04bc59d..0000000000
--- a/app/components/SearchInput.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
- /
-
-
-
-
-
-
-
From 27a87a5f2ee37de8ac8488bd0c44f6bfb78c601e Mon Sep 17 00:00:00 2001
From: Rshig <90143161+rshigg@users.noreply.github.com>
Date: Tue, 27 Jan 2026 08:52:35 -0500
Subject: [PATCH 5/9] apply accent color to logo in header
---
app/components/AppHeader.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue
index 7c1468b631..e3da8fe7bb 100644
--- a/app/components/AppHeader.vue
+++ b/app/components/AppHeader.vue
@@ -24,7 +24,7 @@ const { isConnected, npmUser } = useConnector()
:aria-label="$t('header.home')"
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
>
- ./npmx
+ ./npmx
From a17c86d59173f553f40b82a26af3cbec4575359b Mon Sep 17 00:00:00 2001
From: Daniel Roe
Date: Tue, 27 Jan 2026 14:19:30 +0000
Subject: [PATCH 6/9] fix: remove unused popoverRef
---
app/components/AccentColorPicker.vue | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/app/components/AccentColorPicker.vue b/app/components/AccentColorPicker.vue
index d31d9f1cae..076f3fc4da 100644
--- a/app/components/AccentColorPicker.vue
+++ b/app/components/AccentColorPicker.vue
@@ -1,14 +1,7 @@
@@ -22,13 +15,13 @@ function applyColor(id: AppSettings['accentColorId']) {
:aria-label="color.name"
class="size-6 rounded-full transition-transform duration-150 hover:scale-110 focus-ring aria-selected:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle)"
:style="{ backgroundColor: color.value }"
- @click="applyColor(color.id)"
+ @click="setAccentColor(color.id)"
/>
From c24d1c85b715ba495669c6143f4a58a0a0fbb5dc Mon Sep 17 00:00:00 2001
From: Daniel Roe
Date: Tue, 27 Jan 2026 14:20:04 +0000
Subject: [PATCH 7/9] fix: a11y tweaks
---
app/components/AccentColorPicker.vue | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/components/AccentColorPicker.vue b/app/components/AccentColorPicker.vue
index 076f3fc4da..8a209732a3 100644
--- a/app/components/AccentColorPicker.vue
+++ b/app/components/AccentColorPicker.vue
@@ -5,7 +5,7 @@ const { accentColors, selectedAccentColor, setAccentColor } = useAccentColor()
-
+
From 754316b7500bb3c99d01104c9caefbb7a4d3b9ef Mon Sep 17 00:00:00 2001
From: Daniel Roe
Date: Tue, 27 Jan 2026 14:27:57 +0000
Subject: [PATCH 8/9] refactor: use onPrehydrate for injecting accent colour
var
---
app/app.vue | 3 +++
app/composables/useSettings.ts | 24 ++++++++++++++++++++++++
nuxt.config.ts | 9 ---------
3 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/app/app.vue b/app/app.vue
index 2f5ff3bb5a..7f4f1244d5 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -4,6 +4,9 @@ import { useEventListener } from '@vueuse/core'
const route = useRoute()
const router = useRouter()
+// Initialize accent color before hydration to prevent flash
+initAccentOnPrehydrate()
+
const isHomepage = computed(() => route.path === '/')
useHead({
diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts
index 5d013fd7dd..ac615a9fa7 100644
--- a/app/composables/useSettings.ts
+++ b/app/composables/useSettings.ts
@@ -80,3 +80,27 @@ export function useAccentColor() {
setAccentColor,
}
}
+
+/**
+ * Applies accent color before hydration to prevent flash of default color.
+ * Call this from app.vue to ensure accent color is applied on every page.
+ */
+export function initAccentOnPrehydrate() {
+ // Callback is stringified by Nuxt - external variables won't be available.
+ // Colors must be hardcoded since ACCENT_COLORS can't be referenced.
+ onPrehydrate(() => {
+ const colors: Record = {
+ rose: '#e9aeba',
+ amber: '#fbbf24',
+ emerald: '#34d399',
+ sky: '#38bdf8',
+ violet: '#a78bfa',
+ coral: '#fb7185',
+ }
+ const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')
+ const color = settings.accentColorId ? colors[settings.accentColorId as AccentColorId] : null
+ if (color) {
+ document.documentElement.style.setProperty('--accent-color', color)
+ }
+ })
+}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 93e1de7717..4a82b42b27 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,7 +1,3 @@
-import { ACCENT_COLORS } from './shared/utils/constants'
-
-const accentInitScript = `(function(){var s=JSON.parse(localStorage.getItem('npmx-settings'));if(s&&s.accentColorId){document.documentElement.style.setProperty('--accent-color',${JSON.stringify(ACCENT_COLORS)}[s.accentColorId])}})()`
-
export default defineNuxtConfig({
modules: [
function (_, nuxt) {
@@ -50,11 +46,6 @@ export default defineNuxtConfig({
href: '/opensearch.xml',
},
],
- script: [
- {
- innerHTML: accentInitScript,
- },
- ],
},
},
From ad2ff9197ae4eafabaeb20d2c6e5bd915adab2ff Mon Sep 17 00:00:00 2001
From: Daniel Roe
Date: Tue, 27 Jan 2026 14:32:10 +0000
Subject: [PATCH 9/9] test: update mock in test
---
test/nuxt/components/DateTime.spec.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/test/nuxt/components/DateTime.spec.ts b/test/nuxt/components/DateTime.spec.ts
index 6fd61abd01..a090d0bf30 100644
--- a/test/nuxt/components/DateTime.spec.ts
+++ b/test/nuxt/components/DateTime.spec.ts
@@ -10,6 +10,7 @@ vi.mock('~/composables/useSettings', () => ({
settings: ref({ relativeDates: mockRelativeDates.value }),
}),
useAccentColor: () => ({}),
+ initAccentOnPrehydrate: () => {},
}))
describe('DateTime', () => {