Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c76cc87
buttons & links style adjusted
jellydeck Feb 7, 2026
9b7add1
some cleanup
jellydeck Feb 8, 2026
881330f
few more tweaks 😌
jellydeck Feb 8, 2026
558bcad
fix: rounded on focus-visible
jellydeck Feb 8, 2026
5bc5e5a
for thasmo 🫡
jellydeck Feb 8, 2026
8fedbdd
class as props
jellydeck Feb 8, 2026
1a85202
on active scale down for feedback
jellydeck Feb 8, 2026
dd19dda
border for secondary buttons
jellydeck Feb 8, 2026
af5b977
missing & clipping outlines
jellydeck Feb 8, 2026
49195a6
outlines in compare, readme, settings
jellydeck Feb 9, 2026
faabc73
remove class from props
jellydeck Feb 9, 2026
67858b5
upstream changes for button
jellydeck Feb 9, 2026
2442382
global css cleanup
jellydeck Feb 9, 2026
f4e0178
Merge branch 'main' into button-butter
jellydeck Feb 9, 2026
5488d39
fix: homepage layout shift
jellydeck Feb 9, 2026
f0443a0
removed redundant class
jellydeck Feb 9, 2026
1ae70e7
Merge branch 'main' into button-butter
jellydeck Feb 9, 2026
5e64320
Merge branch 'main' into button-butter
jellydeck Feb 9, 2026
dc2f1e0
Merge branch 'main' into button-butter
jellydeck Feb 9, 2026
469ec33
Merge branch 'main' into button-butter
jellydeck Feb 9, 2026
aa3d772
fixes as per suggestions
jellydeck Feb 9, 2026
dc748ed
styling fixes
jellydeck Feb 10, 2026
d0dd0ed
Merge branch 'main' into button-butter
jellydeck Feb 10, 2026
3c00392
use directional class
jellydeck Feb 10, 2026
5ccfa5c
Merge branch 'button-butter' of https://github.com/jellydeck/npmx.dev…
jellydeck Feb 10, 2026
e9b9527
fix: build issues
jellydeck Feb 10, 2026
837c94b
remove borders from AppHeader
jellydeck Feb 10, 2026
9241f2a
cleanup
jellydeck Feb 10, 2026
135a218
Merge branch 'main' into button-butter
jellydeck Feb 10, 2026
3fa2016
Merge branch 'main' into button-butter
jellydeck Feb 10, 2026
c04c27e
Merge branch 'main' into button-butter
danielroe Feb 11, 2026
cd69d75
sync styles with Readme + some cleanup
jellydeck Feb 11, 2026
270fd6a
Merge branch 'main' into button-butter
jellydeck Feb 11, 2026
31d10ed
upsteam changes
jellydeck Feb 11, 2026
f823696
use mono-font in appheader
jellydeck Feb 11, 2026
c31ed68
Merge branch 'main' into button-butter
jellydeck Feb 11, 2026
754f762
Merge branch 'main' into button-butter
jellydeck Feb 12, 2026
f971245
Merge branch 'main' into button-butter
jellydeck Feb 12, 2026
be46382
Merge branch 'main' into button-butter
jellydeck Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions app/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,22 @@ body {
line-height: 1.6;
}

:focus-visible,
:-moz-focusring {
/* weird Firefox behavior makes it necessary to add `!important`
or otherwise the selector would need to be more specific,
which it explicitly should not be. */
outline: 2px solid var(--accent) !important;
outline-offset: 2px !important;
outline: auto;
}
Comment on lines 225 to 227
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add outline-offset to prevent clipped focus rings in Firefox.

Line 225 sets outline: auto for :-moz-focusring without an offset; in tight or overflow-clipped layouts this can hide the ring (a reported issue in this PR). Adding an offset keeps the focus indicator visible.

Suggested fix
 :-moz-focusring {
   outline: auto;
+  outline-offset: 2px;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
:-moz-focusring {
/* weird Firefox behavior makes it necessary to add `!important`
or otherwise the selector would need to be more specific,
which it explicitly should not be. */
outline: 2px solid var(--accent) !important;
outline-offset: 2px !important;
outline: auto;
}
:-moz-focusring {
outline: auto;
outline-offset: 2px;
}


input:focus {
border: 1px solid var(--accent);
}

input[type='text']:focus-visible,
input[type='search']:focus-visible {
outline: 2px solid color-mix(in oklch, var(--accent) 70%, transparent);
}

select:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

/* Reset dd margin (browser default is margin-left: 40px) */
Expand Down
2 changes: 1 addition & 1 deletion app/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ onKeyStroke(
</div>

<!-- End: Desktop nav items + Mobile menu button -->
<div class="hidden sm:flex flex-shrink-0">
<div class="hidden sm:flex flex-shrink-0 space-x-0.5 md:space-x-2">
<!-- Desktop: Compare link -->
<LinkBase
class="border-none"
Expand Down
46 changes: 26 additions & 20 deletions app/components/Button/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const props = withDefaults(
'disabled'?: boolean
'type'?: 'button' | 'submit'
'variant'?: 'primary' | 'secondary'
'class'?: string
'size'?: 'small' | 'medium'
'keyshortcut'?: string

Expand Down Expand Up @@ -32,15 +33,8 @@ defineExpose({
<template>
<button
ref="el"
class="group cursor-pointer inline-flex gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 disabled:(opacity-40 cursor-not-allowed border-transparent)"
:class="{
'text-sm px-4 py-2': size === 'medium',
'text-xs px-2 py-0.5': size === 'small',
'bg-transparent text-fg hover:enabled:(bg-fg/10) focus-visible:enabled:(bg-fg/10) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(bg-fg text-bg/50))':
variant === 'secondary',
'text-bg bg-fg hover:enabled:(bg-fg/50) focus-visible:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
variant === 'primary',
}"
class="rounded-md outline-none group"
:class="props.class"
:type="props.type"
:disabled="
/**
Expand All @@ -54,17 +48,29 @@ defineExpose({
:aria-keyshortcuts="keyshortcut"
>
<span
v-if="classicon"
:class="[size === 'small' ? 'size-3' : 'size-4', classicon]"
aria-hidden="true"
/>
<slot />
<kbd
v-if="keyshortcut"
class="ms-2 inline-flex items-center justify-center w-4 h-4 text-xs text-fg bg-bg-muted border border-border rounded no-underline"
aria-hidden="true"
class="group cursor-pointer inline-flex gap-x-1.5 relative items-center justify-center rounded-md active:scale-[0.98] font-mono border border-solid border-border transition-[background-color,color,border,outline] duration-200 transition-[border-radius_100ms] after:(content-[''] absolute inset--0.5 rounded-md) outline-transparent group-focus-visible:(outline-2 outline-accent outline-offset-2)"
:class="{
'text-sm px-4 py-2': size === 'medium',
'text-xs px-2 py-0.5': size === 'small',
'text-fg bg-bg hover:(bg-fg/10 border-fg/10)': variant === 'secondary',
'text-bg bg-fg border-fg hover:(bg-fg/80 rounded-xl) active:rounded-xl':
variant === 'primary',
'opacity-40 cursor-not-allowed border-transparent': disabled,
}"
>
{{ keyshortcut }}
</kbd>
<span
v-if="classicon"
:class="[size === 'small' ? 'size-3' : 'size-4', classicon]"
aria-hidden="true"
/>
<slot />
<kbd
v-if="keyshortcut"
class="ms-2 inline-flex items-center justify-center w-4 h-4 text-xs text-fg bg-bg-muted border border-border rounded no-underline"
aria-hidden="true"
>
{{ keyshortcut }}
</kbd>
</span>
</button>
</template>
2 changes: 1 addition & 1 deletion app/components/Button/Group.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const props = defineProps<{
<template>
<component
:is="props.as || 'div'"
class="flex items-center shrink-0 ms-auto [&>*:not(:first-child)]:rounded-s-none [&>*:not(:first-child)]:border-s-0 [&>*:not(:last-child)]:rounded-e-none"
class="flex [&>*:not(:first-child)]:rounded-s-none [&>*:not(:first-child)]:border-s-0 [&>*:not(:last-child)]:rounded-e-none"
>
<slot />
</component>
Expand Down
2 changes: 1 addition & 1 deletion app/components/CallToAction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const socialLinks = computed(() => [
<div
v-for="link in socialLinks"
:key="link.id"
class="group relative grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3 focus-within:ring-2 focus-within:ring-accent/50"
class="group relative grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3 focus-within:ring-2 focus-within:ring-accent"
>
<h3 class="z-1 flex gap-2">
<span :class="link.icon" class="shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />
Expand Down
12 changes: 8 additions & 4 deletions app/components/Header/AccountMenu.client.vue
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,12 @@ function openAuthModal() {
v-if="!isNpmConnected"
type="button"
role="menuitem"
class="w-full px-3 py-2.5 flex items-center gap-3 hover:bg-bg-muted transition-colors text-start rounded-md"
class="w-full px-3 py-2.5 group flex items-center gap-3 hover:bg-bg-muted transition-colors text-start rounded-md"
@click="openConnectorModal"
>
<span class="w-8 h-8 rounded-full bg-bg-muted flex items-center justify-center">
<span
class="w-8 h-8 rounded-full bg-bg-muted group-hover:bg-bg-subtle flex items-center justify-center"
>
<span
v-if="isNpmConnecting"
class="i-carbon-circle-dash w-4 h-4 text-yellow-500 animate-spin"
Expand All @@ -252,10 +254,12 @@ function openAuthModal() {
v-if="!atprotoUser"
type="button"
role="menuitem"
class="w-full px-3 py-2.5 flex items-center gap-3 hover:bg-bg-muted transition-colors text-start rounded-md"
class="w-full px-3 py-2.5 group flex items-center gap-3 hover:bg-bg-muted transition-colors text-start rounded-md"
@click="openAuthModal"
>
<span class="w-8 h-8 rounded-full bg-bg-muted flex items-center justify-center">
<span
class="w-8 h-8 rounded-full bg-bg-muted group-hover:bg-bg-subtle flex items-center justify-center"
>
<span class="i-carbon-cloud w-4 h-4 text-fg-muted" aria-hidden="true" />
</span>
<div class="flex-1 min-w-0">
Expand Down
38 changes: 22 additions & 16 deletions app/components/Link/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const props = withDefaults(
'type'?: never
'variant'?: 'button-primary' | 'button-secondary' | 'link'
'size'?: 'small' | 'medium'
'class'?: string

'keyshortcut'?: string

Expand Down Expand Up @@ -62,35 +63,40 @@ const isButtonMedium = computed(() => props.size === 'medium' && props.variant !
<span
v-if="disabled"
:class="{
'opacity-50 inline-flex gap-x-1 items-center justify-center font-mono border border-transparent rounded-md':
'opacity-50 inline-flex gap-x-1 items-center justify-center font-mono border rounded-md':
isButton,
'text-sm px-4 py-2': isButtonMedium,
'text-xs px-2 py-0.5': isButtonSmall,
'text-bg bg-fg': variant === 'button-primary',
'bg-transparent text-fg': variant === 'button-secondary',
}"
><slot
/></span>
>
<slot />
</span>
<NuxtLink
v-else
class="group inline-flex gap-x-1 items-center justify-center"
:class="{
'underline-offset-[0.2rem] underline decoration-1 decoration-fg/30': !isLinkAnchor && isLink,
'font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200':
isLink,
'font-mono border border-border rounded-md transition-all duration-200': isButton,
'text-sm px-4 py-2': isButtonMedium,
'text-xs px-2 py-0.5': isButtonSmall,
'bg-transparent text-fg hover:(bg-fg/10) focus-visible:(bg-fg/10)':
variant === 'button-secondary',
'text-bg bg-fg hover:(bg-fg/50) focus-visible:(bg-fg/50)': variant === 'button-primary',
}"
class="group inline-flex gap-x-1 items-center justify-center rounded-sm outline-transparent active:scale-[0.98] focus-visible:(outline-2 outline-offset-2 outline-accent)"
:class="[
{
'underline-offset-[0.2rem] underline decoration-1 decoration-fg/30':
!isLinkAnchor && isLink,
'font-mono text-fg hover:(decoration-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200':
isLink,
'border border-solid border-border rounded-md transition-all duration-200': isButton,
'text-sm px-4 py-2': isButtonMedium,
'text-xs px-2 py-0.5': isButtonSmall,
'text-fg bg-bg hover:(bg-fg/10 border-fg/10)': variant === 'button-secondary',
'text-bg bg-fg border-fg hover:(bg-fg/80)': variant === 'button-primary',
},
props.class,
]"
:to="to"
:aria-keyshortcuts="keyshortcut"
:target="isLinkExternal ? '_blank' : undefined"
>
<span
v-if="classicon"
class="text-fg"
:class="[isButtonSmall ? 'size-3' : 'size-4', classicon]"
aria-hidden="true"
/>
Expand All @@ -108,7 +114,7 @@ const isButtonMedium = computed(() => props.size === 'medium' && props.variant !
/>
<kbd
v-if="keyshortcut"
class="ms-2 inline-flex items-center justify-center w-4 h-4 text-xs text-fg bg-bg-muted border border-border rounded no-underline"
class="ms-2 inline-flex items-center justify-center w-4 h-4 text-xs group-hover:text-accent bg-bg-muted border border-border rounded no-underline"
aria-hidden="true"
>
{{ keyshortcut }}
Expand Down
2 changes: 1 addition & 1 deletion app/components/Settings/AccentColorPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ onPrehydrate(el => {

<template>
<fieldset
class="flex items-center gap-4 has-[input:focus-visible]:(outline-solid outline-accent/70 outline-offset-4) rounded-xl w-fit"
class="flex items-center gap-4 has-[input:focus-visible]:(outline-2 outline-solid outline-accent/70 outline-offset-4) rounded-xl w-fit"
>
<legend class="sr-only">{{ $t('settings.accent_colors') }}</legend>
<label
Expand Down
2 changes: 1 addition & 1 deletion app/components/Settings/BgThemePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ onPrehydrate(el => {

<template>
<fieldset
class="flex items-center gap-4 has-[input:focus-visible]:(outline-solid outline-accent/70 outline-offset-4) rounded-xl w-fit"
class="flex items-center gap-4 has-[input:focus-visible]:(outline-2 outline-solid outline-accent/70 outline-offset-4) rounded-xl w-fit"
>
<legend class="sr-only">{{ $t('settings.background_themes') }}</legend>
<label
Expand Down
2 changes: 1 addition & 1 deletion app/components/Settings/Toggle.client.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const checked = defineModel<boolean>({
{{ label }}
</span>
<span
class="inline-flex items-center h-6 w-11 shrink-0 rounded-full border p-0.25 transition-colors duration-200 shadow-sm ease-in-out motion-reduce:transition-none cursor-pointer group-focus-visible:(outline-accent/70 outline-offset-2 outline-solid)"
class="inline-flex items-center h-6 w-11 shrink-0 rounded-full border p-0.25 transition-colors duration-200 shadow-sm ease-in-out motion-reduce:transition-none cursor-pointer group-focus-visible:(outline-2 outline-accent/70 outline-offset-2 outline-solid)"
:class="
checked
? 'bg-accent border-accent group-hover:bg-accent/80'
Expand Down
16 changes: 7 additions & 9 deletions app/pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const setLocale: typeof setNuxti18nLocale = locale => {
</h1>
<button
type="button"
class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0 p-1.5 -mx-1.5"
class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0 p-1.5 -mx-1.5 outline-transparent focus-visible:(outline-2 outline-accent outline-offset-2)"
@click="router.back()"
v-show="router.options.history.state.back !== null"
>
Expand Down Expand Up @@ -162,7 +162,7 @@ const setLocale: typeof setNuxti18nLocale = locale => {
<select
id="language-select"
:value="locale"
class="w-full sm:w-auto min-w-48 bg-bg border border-border rounded-md px-3 py-2 text-sm text-fg focus-visible:outline-accent/70 cursor-pointer duration-200 transition-colors hover:border-fg-subtle"
class="w-full sm:w-auto min-w-48 bg-bg border border-border rounded-md px-3 py-2 text-sm text-fg cursor-pointer duration-200 transition-colors hover:border-fg-subtle"
@change="setLocale(($event.target as HTMLSelectElement).value as typeof locale)"
>
<option v-for="loc in locales" :key="loc.code" :value="loc.code" :lang="loc.code">
Expand Down Expand Up @@ -190,15 +190,13 @@ const setLocale: typeof setNuxti18nLocale = locale => {

<!-- Simple help link for source locale -->
<template v-else>
<a
href="https://github.com/npmx-dev/npmx.dev/tree/main/i18n/locales"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-sm text-fg-muted hover:text-fg transition-colors duration-200 focus-visible:outline-accent/70 rounded"
<LinkBase
classicon="i-carbon:logo-github"
class="font-sans"
to="https://github.com/npmx-dev/npmx.dev/tree/main/i18n/locales"
>
<span class="i-carbon:logo-github w-4 h-4" aria-hidden="true" />
{{ $t('settings.help_translate') }}
</a>
</LinkBase>
</template>
</div>
</section>
Expand Down
Loading