Skip to content

feat: use cursor-pointer on buttons & CmdPalette#2752

Open
aryanpingle wants to merge 1 commit into
npmx-dev:mainfrom
aryanpingle:main
Open

feat: use cursor-pointer on buttons & CmdPalette#2752
aryanpingle wants to merge 1 commit into
npmx-dev:mainfrom
aryanpingle:main

Conversation

@aryanpingle
Copy link
Copy Markdown

🔗 Linked issue

Partially resolves #1760

🧭 Context

This commit removed cursor: pointer from ButtonBase. The reason being, the W3C spec says:

The cursor is a pointer that indicates a link.

The commit was on Valentine's Day, but it is far from being something I love.

CONTRIBUTING.md says:

npmx uses cursor: pointer only for links to match users’ everyday experience. For all other interactive elements, including buttons, use the default cursor (or another appropriate cursor to indicate state).

And the linked issue states in its conclusion:

Based on all this, we're returning the pointer cursor for all interactive elements (without any distinction or definition of what a link is and what a button is). This needs to be updated in the contributing guide and updated throughout our site.

There was a similar PR (#2303) that attempted to do this only for the Download button.
And another PR (#2202) which was closed for potential spam.

To make things worse, at the moment some buttons and dropdowns arbitrarily have cursor: pointer enabled, while more deserving buttons don't:

image
  1. Download button - ❌No pointer cursor
  2. Package manager dropdown button - ✔cursor: pointer AND a fancy mousepress animation
  3. Copy button - ❌No pointer cursor
  4. Sections dropdown button - ❌No pointer cursor

If we're being inconsistent, let's at least err on the side of a better user experience.

I get that the plan was to adhere to the specification / default behaviour, but consider this: if I cared about being "official" more than the UX, I would be using npmjs.com instead of this awesome site.

📚 Description

This PR adds cursor: pointer to some of the pure buttons that one is likely to encounter in the first few pages. Normal buttons + the command palette. My philosophy being, "A pointer cursor indicates that something will happen on clicking the element".

I specifically didn't add it to the dropdowns in case that's another point of contention. I just wanna get this shipped soon so I can enjoy it as an end-user.

W3C this, W3C that. WHO CARES, THE POINTER IS WATCHING. I feel like I'm taking crazy pills, but I'm not because I ran out of crazy pills, I need more.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment May 15, 2026 3:05pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview May 15, 2026 3:05pm
npmx-lunaria Ignored Ignored May 15, 2026 3:05pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Style
    • Enhanced visual feedback on interactive buttons and controls throughout the application by improving cursor styling to better indicate clickability. Updated components across header menus, search functionality, modals, package details, and more for improved user experience.

Walkthrough

This PR adds the cursor-pointer CSS class to interactive buttons and controls throughout the application. Changes span 12 components and 2 pages, applying consistent pointer cursor styling to improve user interaction feedback across the entire interface.

Changes

Cursor Pointer Styling

Layer / File(s) Summary
Header and navigation components
app/components/AppHeader.vue, app/components/Header/AccountMenu.client.vue, app/components/Header/MobileMenu.client.vue, app/components/SearchProviderToggle.client.vue
Command Palette buttons in desktop and mobile headers gain cursor-pointer styling, as do the npm CLI and Atmosphere connection options in the account menu and all search provider toggle menu buttons (toggle, NPM, and Algolia options).
Search and filter controls
app/components/InstantSearch.vue, app/components/ViewModeToggle.vue, app/pages/index.vue
Instant search toggle buttons and view mode toggle buttons now include cursor-pointer, along with the home page search submit button. InstantSearch buttons also receive explicit type="button" attributes.
Package page interactive controls
app/components/Modal.client.vue, app/components/Package/DownloadButton.vue, app/components/Package/ListToolbar.vue, app/components/Package/Versions.vue, app/components/Package/WeeklyDownloadStats.vue, app/pages/package/[[org]]/[name].vue
Modal close button, package download button, list toolbar sort and selection controls, community distribution button, weekly download statistics chart button, and package detail page copy-as-Markdown button all receive cursor-pointer styling to indicate clickability.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: adding cursor-pointer styling to buttons and the command palette.
Description check ✅ Passed The description is detailed and directly related to the changeset, providing context about the cursor styling changes and linked issue #1760.
Linked Issues check ✅ Passed The PR meets the objective of issue #1760 by adding cursor-pointer to interactive elements (buttons and command palette) across the site.
Out of Scope Changes check ✅ Passed All changes are within scope: cursor-pointer styling is added consistently to buttons and the command palette as specified in the linked issue requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

Hello! Thank you for opening your first PR to npmx, @aryanpingle! 🚀

Here’s what will happen next:

  1. Our GitHub bots will run to check your changes.
    If they spot any issues you will see some error messages on this PR.
    Don’t hesitate to ask any questions if you’re not sure what these mean!

  2. In a few minutes, you’ll be able to see a preview of your changes on Vercel

  3. One or more of our maintainers will take a look and may ask you to make changes.
    We try to be responsive, but don’t worry if this takes a few days.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/components/InstantSearch.vue 0.00% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/Header/AccountMenu.client.vue`:
- Line 228: The account menu has inconsistent pointer affordance: ensure all
menuitem buttons in the AccountMenu.vue component use the same clickable classes
by adding the missing cursor-pointer to the connected-account menuitem buttons
(the ones that currently have class="w-full text-start gap-x-3 border-none" at
the other two menu entries) so they match the menuitem that uses class="w-full
text-start gap-x-3 border-none cursor-pointer"; update the class attribute on
those menuitem buttons to include cursor-pointer so all items behave
consistently.

In `@app/components/Package/ListToolbar.vue`:
- Line 181: In ListToolbar.vue remove the inline focus-visible utility classes
from the button element that currently includes "focus-visible:ring-2
focus-visible:ring-fg focus-visible:ring-offset-2 focus-visible:ring-offset-bg";
update the element's class attribute to drop those four tokens so the button
relies on the global focus-visible rules defined in main.css, leaving the other
utilities (p-2.5, rounded-md, border, bg-bg-subtle, text-fg-muted,
hover:text-fg, hover:border-border-hover, transition-colors, duration-200,
cursor-pointer) intact.

In `@app/components/Package/WeeklyDownloadStats.vue`:
- Line 406: Remove the inline focus-visible utility from the button class list
in WeeklyDownloadStats.vue: the class token "focus-visible:outline-accent/70"
should be deleted from the ButtonBase/button element's class attribute so it
relies on the global focus-visible rule defined in main.css; search for the
ButtonBase render (button element) and strip that specific token from its class
string and verify no other inline focus-visible utilities remain in the
component.

In `@app/components/ViewModeToggle.vue`:
- Line 19: The cursor-pointer class is only on the inactive state string—move it
out of the conditional state classes and into the base class for both buttons in
ViewModeToggle.vue so both active and inactive buttons are consistently
interactive; locate the class bindings on the two toggle buttons (the template
lines using the ternary class strings around 'text-fg-muted' / active classes)
and remove 'cursor-pointer' from the conditional branches and add it to the
shared/base class string for each button.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb101137-b249-4d64-9b9b-81885659ccff

📥 Commits

Reviewing files that changed from the base of the PR and between 01e3d83 and 4be7f22.

📒 Files selected for processing (13)
  • app/components/AppHeader.vue
  • app/components/Header/AccountMenu.client.vue
  • app/components/Header/MobileMenu.client.vue
  • app/components/InstantSearch.vue
  • app/components/Modal.client.vue
  • app/components/Package/DownloadButton.vue
  • app/components/Package/ListToolbar.vue
  • app/components/Package/Versions.vue
  • app/components/Package/WeeklyDownloadStats.vue
  • app/components/SearchProviderToggle.client.vue
  • app/components/ViewModeToggle.vue
  • app/pages/index.vue
  • app/pages/package/[[org]]/[name].vue

v-if="!isNpmConnected"
role="menuitem"
class="w-full text-start gap-x-3 border-none"
class="w-full text-start gap-x-3 border-none cursor-pointer"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Apply the same pointer affordance to all account menu menuitem buttons.

These two updates are good, but the connected account menuitem buttons in the same menu (Line 145 and Line 188) are still clickable without cursor-pointer, so the menu remains inconsistent.

Suggested minimal follow-up
- class="w-full text-start gap-x-3 border-none"
+ class="w-full text-start gap-x-3 border-none cursor-pointer"
- class="w-full text-start gap-x-3 border-none"
+ class="w-full text-start gap-x-3 border-none cursor-pointer"

Also applies to: 254-254

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/Header/AccountMenu.client.vue` at line 228, The account menu
has inconsistent pointer affordance: ensure all menuitem buttons in the
AccountMenu.vue component use the same clickable classes by adding the missing
cursor-pointer to the connected-account menuitem buttons (the ones that
currently have class="w-full text-start gap-x-3 border-none" at the other two
menu entries) so they match the menuitem that uses class="w-full text-start
gap-x-3 border-none cursor-pointer"; update the class attribute on those
menuitem buttons to include cursor-pointer so all items behave consistently.

v-if="!searchContext || currentSort.key !== 'relevance'"
type="button"
class="p-2.5 rounded-md border border-border bg-bg-subtle text-fg-muted hover:text-fg hover:border-border-hover transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-2 focus-visible:ring-offset-bg"
class="p-2.5 rounded-md border border-border bg-bg-subtle text-fg-muted hover:text-fg hover:border-border-hover transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-2 focus-visible:ring-offset-bg cursor-pointer"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Remove inline focus-visible utilities on button element.

The inline focus-visible classes (focus-visible:ring-2, focus-visible:ring-fg, focus-visible:ring-offset-2, focus-visible:ring-offset-bg) should not be applied directly to button elements. The global CSS rule in main.css provides consistent focus-visible styling for all buttons.

♻️ Proposed fix to rely on global focus-visible styling
-            class="p-2.5 rounded-md border border-border bg-bg-subtle text-fg-muted hover:text-fg hover:border-border-hover transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-2 focus-visible:ring-offset-bg cursor-pointer"
+            class="p-2.5 rounded-md border border-border bg-bg-subtle text-fg-muted hover:text-fg hover:border-border-hover transition-colors duration-200 cursor-pointer"

Based on learnings: In the npmx.dev project, focus-visible styling for button and select elements is implemented globally in app/assets/main.css. Do not apply per-element inline utility classes on these elements; rely on the global rule for consistency and maintainability.

📝 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
class="p-2.5 rounded-md border border-border bg-bg-subtle text-fg-muted hover:text-fg hover:border-border-hover transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-2 focus-visible:ring-offset-bg cursor-pointer"
class="p-2.5 rounded-md border border-border bg-bg-subtle text-fg-muted hover:text-fg hover:border-border-hover transition-colors duration-200 cursor-pointer"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/Package/ListToolbar.vue` at line 181, In ListToolbar.vue
remove the inline focus-visible utility classes from the button element that
currently includes "focus-visible:ring-2 focus-visible:ring-fg
focus-visible:ring-offset-2 focus-visible:ring-offset-bg"; update the element's
class attribute to drop those four tokens so the button relies on the global
focus-visible rules defined in main.css, leaving the other utilities (p-2.5,
rounded-md, border, bg-bg-subtle, text-fg-muted, hover:text-fg,
hover:border-border-hover, transition-colors, duration-200, cursor-pointer)
intact.

type="button"
@click="openChartModal"
class="text-fg-subtle hover:text-fg transition-colors duration-200 inline-flex items-center justify-center min-w-6 min-h-6 -m-1 p-1 focus-visible:outline-accent/70 rounded"
class="text-fg-subtle hover:text-fg transition-colors duration-200 inline-flex items-center justify-center min-w-6 min-h-6 -m-1 p-1 focus-visible:outline-accent/70 rounded cursor-pointer"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Remove inline focus-visible utility on button element.

The inline focus-visible:outline-accent/70 class should not be applied directly to ButtonBase (which renders as a button element). The global CSS rule in main.css provides consistent focus-visible styling for all buttons.

♻️ Proposed fix to rely on global focus-visible styling
-          class="text-fg-subtle hover:text-fg transition-colors duration-200 inline-flex items-center justify-center min-w-6 min-h-6 -m-1 p-1 focus-visible:outline-accent/70 rounded cursor-pointer"
+          class="text-fg-subtle hover:text-fg transition-colors duration-200 inline-flex items-center justify-center min-w-6 min-h-6 -m-1 p-1 rounded cursor-pointer"

Based on learnings: In the npmx.dev project, focus-visible styling for button and select elements is implemented globally in app/assets/main.css. Individual buttons or selects in Vue components should not rely on inline focus-visible utility classes; rely on the global rule.

📝 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
class="text-fg-subtle hover:text-fg transition-colors duration-200 inline-flex items-center justify-center min-w-6 min-h-6 -m-1 p-1 focus-visible:outline-accent/70 rounded cursor-pointer"
class="text-fg-subtle hover:text-fg transition-colors duration-200 inline-flex items-center justify-center min-w-6 min-h-6 -m-1 p-1 rounded cursor-pointer"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/Package/WeeklyDownloadStats.vue` at line 406, Remove the
inline focus-visible utility from the button class list in
WeeklyDownloadStats.vue: the class token "focus-visible:outline-accent/70"
should be deleted from the ButtonBase/button element's class attribute so it
relies on the global focus-visible rule defined in main.css; search for the
ButtonBase render (button element) and strip that specific token from its class
string and verify no other inline focus-visible utilities remain in the
component.

viewMode === 'cards'
? 'bg-bg-subtle text-fg border-fg-subtle'
: 'text-fg-muted hover:text-fg border-transparent'
: 'text-fg-muted hover:text-fg border-transparent cursor-pointer'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make cursor behaviour consistent across both toggle states.

cursor-pointer is currently applied only when a mode is inactive. Since both buttons remain interactive, move cursor-pointer to the base class so active and inactive states behave consistently.

Suggested adjustment
- class="flex items-center px-2.5 py-2 text-sm font-medium rounded-sm border transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
+ class="flex items-center px-2.5 py-2 text-sm font-medium rounded-sm border transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1 cursor-pointer"
- : 'text-fg-muted hover:text-fg border-transparent cursor-pointer'
+ : 'text-fg-muted hover:text-fg border-transparent'

(Apply the same pattern to both buttons.)

Also applies to: 34-34

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/ViewModeToggle.vue` at line 19, The cursor-pointer class is
only on the inactive state string—move it out of the conditional state classes
and into the base class for both buttons in ViewModeToggle.vue so both active
and inactive buttons are consistently interactive; locate the class bindings on
the two toggle buttons (the template lines using the ternary class strings
around 'text-fg-muted' / active classes) and remove 'cursor-pointer' from the
conditional branches and add it to the shared/base class string for each button.

@gameroman gameroman added the needs discussion An idea that needs more discussion to understand the scope and impact. label May 15, 2026
Copy link
Copy Markdown
Contributor

@graphieros graphieros left a comment

Choose a reason for hiding this comment

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

@aryanpingle 🙏 thank you for your PR, it's been a while indeed since the decision was taken to have the pointer on buttons.

I think the PR should solve all the cases to be merged, to avoid a partial situation which would remain confusing for users. Since this is my opinion, and perhaps other maintainers have a different one, I'm just commenting and not requesting changes.

@aryanpingle
Copy link
Copy Markdown
Author

@graphieros I get what you mean about the partial solution problem, in that it may be confusing for users to see some buttons have default cursors and pointer for others.
That said, this is already the case, as in the screenshot I've attached as well as other places on the site.


For a comprehensive fix across all buttons, there are two scenarios:

  1. Setting cursor: pointer on the ButtonBase component - this will affect every single button, and we can simply revert this PR
  2. Setting cursor: pointer on each button as needed - it will be an extension of this PR (since I've not covered every case)

So from a tech standpoint, it's either easily revertible or a first step.
But from a UX standpoint, it's an improvement that we either have today or sometime down the line.
And as someone who uses the site almost daily, I'd rather have it sooner than later.

Happy to hear any thoughts!

@graphieros
Copy link
Copy Markdown
Contributor

@aryanpingle I feel well aligned with your approach.
@knowler, @alexdln, @MatteoGabriele what do you think ?

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

Labels

needs discussion An idea that needs more discussion to understand the scope and impact.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use pointer cursor for interactive elements

3 participants