desktop-principles
Desktop-specific UX principles - hover states, pointer precision, keyboard shortcuts, multi-window, focus management. Covers macOS, Windows, Linux, web desktop.
Install / Use
npx skills add AThevon/genjutsu --skill desktop-principlesInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Customer SupportSupported Platforms
Our assessment of desktop-principles
desktop-principles scores 91/100 on our quality scale, 130th of 320 Customer Support skills we index (top 41%).
Its SKILL.md is 13 KB long, well organised into 14 sections with 20 code examples: a thorough specification that gives an agent plenty to work with.
It has 370 GitHub stars, a meaningful sign that others use it.
Maintenance, license and trust
- The repository was last updated 26 days ago, so desktop-principles is actively maintained.
- No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
- Its trust signals score 88/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-10-05. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
desktop-principles compared with similar skills
All 4 of these similar skills score higher than desktop-principles; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| desktop-principles (this skill)by AThevon | 91 | 370 | 26d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 90.8k | 19d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 74.4k | today | CLAUDE.md |
| Scraplingby D4Vinci | 100 | 85.7k | today | MCP Server |
| crawl4aiby unclecode | 100 | 84.8k | 9d ago | MCP Server |
Frequently asked questions
- How do I install desktop-principles?
- Run
npx skills add AThevon/genjutsu --skill desktop-principles. The install tabs above show the steps for each supported agent. - Which AI agents does desktop-principles work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is desktop-principles safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It declares no license and scores 88/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is desktop-principles still maintained?
- The repository was last updated 26 days ago, so desktop-principles is actively maintained.
Skill content
View source on GitHubname: desktop-principles description: "Desktop-specific UX principles - hover states, pointer precision, keyboard shortcuts, multi-window, focus management. Covers macOS, Windows, Linux, web desktop." metadata: internal: true
Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in
_jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.
Desktop Principles
Desktop UX context. Loaded when desktop is detected (macOS, Windows, Linux desktop, web desktop). Concise rules here. Deep-dive in
references/.
Hover States Are Mandatory
Hover is the primary affordance signal on desktop, the inverse of mobile. A pointer hovering over a target without immediate visual feedback feels broken: users rely on :hover to confirm an element is interactive before committing to a click. Every clickable surface must have a distinct hover style, ideally with a 100-200ms transition so the change is perceptible without feeling sluggish.
CSS - hover styles for interactive elements:
.button {
background: var(--surface);
transition: background 120ms ease-out, transform 120ms ease-out;
}
.button:hover {
background: var(--surface-hover);
transform: translateY(-1px);
}
.button:active {
transform: translateY(0);
}
SwiftUI - .onHover is the portable hover signal; .hoverEffect is iOS/iPadOS-only and must be #if-gated:
| Modifier | Availability | Notes |
|---|---|---|
| .onHover { Bool } | iOS 13.4+, iPadOS 13.4+, Mac Catalyst 13.4+, macOS 10.15+, visionOS 1.0+ | the one that works on macOS |
| .onContinuousHover { HoverPhase } | iOS 17.0+, macOS 14.0+, tvOS 17.0+, visionOS 1.0+ | pointer position, not just in/out |
| .hoverEffect(_:) | iOS 13.4+, iPadOS 13.4+, Mac Catalyst 13.4+, tvOS 16.0+, visionOS 1.0+ - macOS unavailable | compile error in a macOS target |
| .pointerStyle(_:) | macOS 15.0+, visionOS 2.0+ | change the macOS cursor over a view |
struct ToolbarButton: View {
@State private var hovering = false
var body: some View {
Image(systemName: "square.and.arrow.up")
.padding(8)
.background(hovering ? Color.gray.opacity(0.15) : .clear)
.onHover { hovering = $0 }
.animation(.easeOut(duration: 0.12), value: hovering)
.pointerHighlight() // see the extension below - .hoverEffect is UNAVAILABLE on macOS
}
}
// `.hoverEffect` is @available(macOS, unavailable). Gate it with #if, never with #available.
extension View {
@ViewBuilder func pointerHighlight() -> some View {
#if os(macOS)
self
#else
self.hoverEffect(.highlight)
#endif
}
}
Compose Desktop - onPointerEvent or hoverable + interactionSource:
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ToolbarButton(onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
val hovered by interactionSource.collectIsHoveredAsState()
Box(
modifier = Modifier
.hoverable(interactionSource)
.background(if (hovered) Color.LightGray.copy(alpha = 0.15f) else Color.Transparent)
.clickable(onClick = onClick)
.padding(8.dp),
) { Icon(Icons.Default.Share, contentDescription = "Share") }
}
Pointer Precision
Mouse and trackpad pointers are far more accurate than thumbs, so desktop targets can be smaller than the 44pt mobile minimum. Common ranges are 24-32px for icon buttons, 28-36px for toolbar items. WCAG 2.5.8 Target Size (Minimum), Level AA, sets the floor at 24x24 CSS pixels for all pointer input - it is not desktop-specific. A sub-24px target still passes under the spacing exception: draw a 24px-diameter circle centred on each undersized target and no two circles may intersect. Apple's macOS HIG is stricter in spirit and looser in numbers: 28x28 pt default control size, 20x20 pt absolute minimum.
Fitts's Law in practice: the time to acquire a target shrinks with size and grows with distance. Screen edges and corners are infinite-depth targets - the cursor stops there regardless of overshoot. Put high-frequency global controls (close window, system menu, app dock) in corners and along edges. macOS menubar and Windows taskbar are textbook applications: edge-anchored, zero-overshoot acquisition.
Keyboard Shortcuts (first-class)
Desktop users expect parity with native conventions. Missing ⌘+F in a list-heavy app is not minimalism, it is a bug.
| Action | macOS | Windows / Linux |
|---|---|---|
| New | ⌘+N | Ctrl+N |
| Close window | ⌘+W | Ctrl+W |
| Quit app | ⌘+Q | Alt+F4 |
| Settings / Preferences | ⌘+, | Ctrl+, |
| Find | ⌘+F | Ctrl+F |
| Toggle (comment, sidebar...) | ⌘+/ | Ctrl+/ |
| Save | ⌘+S | Ctrl+S |
| Command palette | ⌘+K or ⌘+Shift+P | Ctrl+K or Ctrl+Shift+P |
Web - detect Ctrl vs Cmd correctly:
// Prefer event.metaKey on macOS, event.ctrlKey elsewhere.
// navigator.platform is deprecated but still pragmatic; fall back to userAgent.
// navigator.platform is always a non-empty string where it exists, so `||` never falls through.
// Prefer UA-CH where available, then platform, then the UA string.
const isMac = /Mac|iPhone|iPad/.test(
navigator.userAgentData?.platform ?? navigator.platform ?? navigator.userAgent ?? ""
);
window.addEventListener("keydown", (e) => {
const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
if (cmdOrCtrl && e.key.toLowerCase() === "k") {
e.preventDefault();
openCommandPalette();
}
});
SwiftUI - .keyboardShortcut binds to menu commands:
Button("New Document", action: newDoc)
.keyboardShortcut("n", modifiers: .command)
Button("Find", action: focusSearch)
.keyboardShortcut("f", modifiers: .command)
Compose Desktop - onKeyEvent + KeyShortcut:
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.commandShortcut(key: Key, onTrigger: () -> Unit) =
onKeyEvent { event ->
if (event.type == KeyEventType.KeyDown && event.isMetaPressed && event.key == key) {
onTrigger(); true
} else false
}
// In MenuBar:
MenuBar {
Menu("File") {
Item("New", shortcut = KeyShortcut(Key.N, meta = true), onClick = ::newDoc)
Item("Find", shortcut = KeyShortcut(Key.F, meta = true), onClick = ::focusSearch)
}
}
Multi-Window Patterns
Desktop users keep windows side by side. A new window is the right answer when:
- A task runs long enough that the user wants to keep working in the main window (rendering, export, sync log).
- The user is comparing two parallel contexts (two documents, two chats, two issues).
- The app is document-based and each document is a peer (Pages, Figma files, Xcode projects).
A new window is the wrong answer for transient confirmations, brief settings panels, or anything that can live in a sheet or popover.
SwiftUI - WindowGroup for document-style, Window for singletons:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup("Document") { DocumentView() } // peer windows, one per doc
Window("Inspector", id: "inspector") { InspectorView() }
.windowResizability(.contentSize) // tracks intrinsic content size
Settings { SettingsView() } // ⌘+, target on macOS
}
}
Compose Desktop - Window composables, application scope:
fun main() = application {
val docs = remember { mutableStateListOf(Document()) }
docs.forEach { doc ->
Window(onCloseRequest = { docs.remove(doc) }, title = doc.title) {
DocumentView(doc)
}
}
if (showInspector) {
Window(onCloseRequest = { showInspector = false }, title = "Inspector") {
InspectorView()
}
}
}
State sharing: windows are views over the same model. Hold the source of truth in a singleton or a DI-scoped object (SwiftUI @Observable injected via environment, Compose koin or viewModel-equivalent). Never duplicate state per window - reconciling diverging copies is a graveyard of bugs.
Focus Management
Keyboard navigation is a first-class input on desktop. Tab order must be sane, focus rings must be visible, and removing them without an alternative is an accessibility regression.
SwiftUI - @FocusState drives field focus:
struct LoginForm: View {
enum Field { case email, password }
@State private var email = ""
@State private var password = ""
@FocusState private var focused: Field?
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($focused, equals: .email)
.onSubmit { focused = .password }
SecureField("Password", text: $password)
.focused($focused, equals: .password)
.onSubmit(submit)
}
.onAppear { focused = .email }
}
}
Compose - FocusRequester + LocalFocusManager:
val emailFocus = remember { FocusRequester() }
val passwordFocus = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
TextField(
value = email, onValueChange = { email = it },
modifier = Modifier.focusRequester(emailFocus),
// Without keyboardOptions.imeAction the IME never emits Next and onNext never fires.
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { passwordFocus.requestFocus() }),
)
TextField(
value = password, onValueChange = { password = it },
modifier = Modifier.focusRequester(passwordFocus).focusable(),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus(); submit() }),
)
LaunchedEffect(Unit) { emailFocus.requestFocus() }
Web - tabindex + :focus-visible:
.button {
/* Never `outline: none` without an alternative. */
}
.button:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
<!-- tabindex="0" puts a non-interactive element in tab order -->
<div role="button" tabindex="0" class="button">Custom button</div>
Information Density
Desktop users sit on a 13-32 inch screen with a precise pointer and full keyboard. They can - and want to - parse more information per viewport than on mobile. Use an 8px base grid (vs 4-8px mobile), persistent sidebars instead of bottom tabs, command palettes (⌘K) for power users, and dense data tables when the data warrants it. Linear, Things 3, and Notion are the touchstones: information-rich without feeling cramped, every pixel earns its keep.
Subtle Animations Doctrine
Desktop apps are stared at for hours. Animations that feel delightful once become unbearable on the hundredth repetition. Prefer short, purely functional motion: opacity and small translations under 200ms, no bounces on routine interactions, no playful overshoots on hover. Save expressive motion for one-shot moments (onboarding, success states), never daily UI.
/* BAD - every hover bounces for 600ms, exhausting after the third use */
.card {
transition: transform 600ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.card:hover { transform: scale(1.05); }
/* GOOD - 100ms opacity, almost subliminal, never tires */
.card {
opacity: 0.92;
transition: opacity 100ms ease-out;
}
.card:hover { opacity: 1; }
Anti-Patterns (BAD / GOOD)
1. Hiding navigation behind a hamburger on desktop
<!-- BAD - 1440px viewport, infinite room, but nav is collapsed -->
<header>
<button class="hamburger" aria-label="Menu">☰</button>
</header>
<nav class="drawer hidden">...</nav>
<!-- GOOD - persistent sidebar on desktop, collapsible if the user wants it -->
<aside class="sidebar">
<nav>
<a href="/inbox">Inbox</a>
<a href="/projects">Projects</a>
<a href="/archive">Archive</a>
</nav>
<button class="collapse-toggle" aria-label="Collapse sidebar">⇤</button>
</aside>
2. No keyboard shortcuts for primary actions
// BAD - "New" is buried in a menu, no shortcut, every creation is 3 clicks
<Toolb
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
90.8kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
74.4kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
Scrapling
85.7k🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
crawl4ai
84.8kOpen-source web crawler and scraper for LLMs and AI agents: any website into clean, LLM-ready Markdown. Run it yourself, or use Crawl4AI Cloud with one key.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
