HomeProjectsPlatform Aware Keyboard Shortcuts

Platform-Aware Keyboard Shortcuts

Published Aug 19, 2025
Updated Jan 29, 2026
2 minutes read

Building Platform-Aware Keyboard Shortcuts: A Cross-Platform UX Enhancement

Internal Development Case Study

The Problem

While working on the portfolio's search functionality, we discovered a subtle but important UX inconsistency: all keyboard shortcut displays showed the macOS Command symbol (⌘) regardless of the user's operating system. Windows and Linux users were seeing F when they should see Ctrl+F, creating confusion and breaking platform conventions.

This might seem like a minor detail, but these small inconsistencies compound to create a less polished user experience. Users expect applications to respect their platform's conventions.

The Investigation

During a search overlay review, we identified multiple locations where keyboard shortcuts were hardcoded:

Each location had duplicate logic for displaying shortcuts, and none detected the user's platform.

The Solution: A Platform-Aware System

Rather than fixing each location individually, we built a reusable system that automatically adapts to the user's platform.

Core Architecture

Platform Detection

export function isMac(): boolean {
  return typeof navigator !== 'undefined' && 
    navigator.platform.toUpperCase().indexOf('MAC') >= 0;
}

Unified Component Interface

interface KeyboardShortcutProps {
  keys: string; // e.g., "cmd+f", "shift+c", "enter"
  className?: string;
}

Smart Key Mapping

The system translates logical keys to platform-appropriate symbols:

Input KeymacOS OutputWindows/Linux Output
cmd+f⌘F (with icon)Ctrl+F
cmd+shift+c⌘⇧CCtrl+Shift+C
alt+tab⌥⇥Alt+Tab

Rendering Strategy

For Mac users seeing cmd+f, we maintain the visual design with the Command icon:

<div className="flex items-center gap-1">
  <Command className="h-3 w-3" />
  <span>F</span>
</div>

For other platforms, we render clean text in a kbd element:

<kbd className="rounded border px-1.5 py-0.5">Ctrl+F</kbd>

Implementation Challenges

SSR Compatibility

Since we're using Next.js, we needed to handle server-side rendering where navigator isn't available:

// Safe platform detection that works during SSR
const isMac = typeof navigator !== 'undefined' && 
  navigator.platform.toUpperCase().indexOf('MAC') >= 0;

Tailwind Integration

The existing design system used consistent kbd styling across components. Our solution maintains this by applying the same classes programmatically:

<kbd className="rounded border border-kbd-border bg-kbd-background px-1.5 py-0.5 text-kbd-foreground text-xs">
  {formattedKeys}
</kbd>

Design System Consistency

The Command icon has specific visual treatment in the design. We preserved this for Mac users while ensuring Windows/Linux users get equally polished text-based shortcuts.

Code Elimination

The refactor eliminated significant code duplication across 6 components:

Before:

// Repeated in every component
const isMac = navigator.platform.indexOf('Mac') >= 0;
 
{isMac ? (
  <>
    <Command className="h-3 w-3" />
    <span>F</span>
  </>
) : (
  <span>Ctrl+F</span>
)}

After:

// Single line in every component
<KeyboardShortcut keys="cmd+f" />

Technical Outcomes

Performance

Developer Experience

User Experience

Future Enhancements

This foundation enables several future improvements:

  1. Tooltip Integration: Hover states could show additional context
  2. Accessibility: Screen readers could announce platform-appropriate shortcuts
  3. Customization: Users could potentially choose their preferred notation style
  4. Documentation: Automatically generate platform-specific help docs

Lessons Learned

Start With Abstractions

Rather than fixing the immediate problem in each location, building the reusable system saved time and prevents future inconsistencies.

Small Details Matter

Platform conventions seem minor but significantly impact perceived polish and professionalism.

TypeScript Pays Off

Strong typing caught several edge cases during implementation and makes the API self-documenting for future developers.

This is one of those win-win situations that actually lives up to the hype. Users get the experience that just feels right, and your codebase stays organized instead of turning into spaghetti.