← All motion systems
Motion System

GitHub Primer Design System

Minimal, fast, and unobtrusive — motion that stays out of the way of the work.

get_motion("github")
Browse all

Motion System: GitHub Primer Design System

Category: Developer Tools Minimal, fast, and unobtrusive — motion that stays out of the way of the work.

Motion Philosophy

Developers using GitHub are in flow: reading code, reviewing pull requests, scanning issues, writing commit messages. The interface must never break that flow. Motion in Primer is not a feature — it is a maintenance function. It confirms that a button was clicked, orients the user when a panel opens, and smooths over state changes that would otherwise cause a jarring jump. When done correctly, users do not notice the motion at all; they only notice the absence of it if it were removed.

The speed of the system reflects the mental model of its users. Developers expect tools to respond instantly. A 300ms modal open would feel sluggish to someone who has muscle-memory'd keyboard shortcuts and expects the interface to keep up with their thinking. Primer transitions sit in the 80–150ms range for most interactions — fast enough to feel instant while still providing the visual continuity that distinguishes a transition from a snap. State changes on code elements — syntax highlighting, selection, line number highlights — happen at 0ms. The code is the content; animating it adds nothing and risks distraction.

The Octocat and GitHub's mascot animations exist as a distinct expressive layer, entirely separate from the functional motion system. These Lottie-based illustrations appear in empty states, 404 pages, and celebration moments (the merge confetti on a merged PR, for example). They are authored in After Effects and treated as content — they do not follow the Primer motion tokens and are not considered part of the functional UI animation system. The two layers never mix: an Octocat animation never plays inside a functional component.

Duration Scale

TokenValueUse
duration-instant0msCode elements, syntax highlighting, line selections, diff highlights
duration-fast80msHover fills, focus rings, icon state changes, tooltip appear
duration-default120msDropdown menus, popovers, context menus
duration-moderate150msModals, dialogs, side sheets
duration-slow200msPage-level navigation, deep panel transitions
duration-expressive400ms–800msOctocat/Lottie illustrations only — not used in functional UI

Easing

TokenCurveUse
ease-outcubic-bezier(0, 0, 0.5, 1)Primary entering ease — snappy deceleration
ease-incubic-bezier(0.5, 0, 1, 1)Primary exiting ease — quick departure
ease-in-outcubic-bezier(0.5, 0, 0.5, 1)Repositioning, accordion height, tab indicator slide
ease-linearcubic-bezier(0, 0, 1, 1)Progress bars, loading spinners, skeleton shimmer

Primer's easing curves are intentionally conservative — no exotic or branded curves. The curves are close to standard browser ease values to keep CSS predictable, consistent, and easy for contributors to reason about.

Spring Configs (Framer Motion)

Spring physics are rarely used in Primer. The design system defaults to CSS transitions for all functional UI. Springs are documented for teams building richer interactive experiences on top of Primer.

  • Default (modal, dialog): stiffness: 400, damping: 40, mass: 0.8 — resolves quickly, minimal overshoot
  • Popover: stiffness: 500, damping: 45, mass: 0.7 — very fast settle, near-instant
  • Sheet (mobile bottom sheet, side panel on mobile): stiffness: 300, damping: 35, mass: 1 — slight overshoot acceptable
  • Disabled (reduced motion): stiffness: 2000, damping: 200, mass: 1 — resolves in under 16ms

No bouncy configs. No underdamped springs. Overshoot only acceptable in Sheet where the user is actively dragging and the physics communicates gesture momentum.

Stagger Patterns

  • File tree items (initial load): 8ms between each item, capped at 10 items — barely perceptible, just enough to prevent a pop
  • Repository cards (explore, org page): 16ms between each card, capped at 8 cards
  • Comment thread items: no stagger — comments load as a block
  • Diff lines: no stagger — diff renders atomically; partial rendering would impede code reading
  • Notification items: 12ms between each notification, capped at 6

Stagger intervals are deliberately small. The goal is to prevent simultaneous pop-in, not to create a visible cascade effect.

Enter / Exit Patterns

Fade (tooltips, hover cards, branch name chips)

enter: opacity 0→1, duration: duration-fast (80ms), ease: ease-out
exit: opacity 1→0, duration: duration-fast (80ms), ease: ease-in

Scale + Fade (dropdown menus, select menus, context menus)

enter: opacity 0→1, scale 0.97→1 (transform-origin: top), duration: duration-default (120ms), ease: ease-out
exit: opacity 1→0, scale 1→0.97, duration: duration-fast (80ms), ease: ease-in

Dialog / Modal

enter: opacity 0→1, scale 0.97→1, translateY 4px→0, duration: duration-moderate (150ms), ease: ease-out
backdrop: opacity 0→0.4, duration: duration-moderate (150ms), ease: ease-out
exit: opacity 1→0, duration: duration-fast (80ms), ease: ease-in

Slide (ActionList drawer, side panel)

enter: translateX -100%→0 (left panel) or translateX 100%→0 (right panel), duration: duration-moderate (150ms), ease: ease-out
exit: translateX 0→-100% or 0→100%, duration: duration-fast (80ms), ease: ease-in

Expandable section (accordion, collapsed diff sections, "show more" in comments)

open: height 0→content-height (max-height transition), duration: duration-moderate (150ms), ease: ease-in-out
close: height content-height→0, duration: duration-fast (80ms), ease: ease-in-out
chevron rotation: 0deg→90deg synchronized with open, ease: ease-in-out

Tab indicator (repository tabs: Code / Issues / Pull Requests)

active indicator bar: translateX slides to new tab position, duration: duration-default (120ms), ease: ease-in-out
tab content: crossfade, opacity 0→1, duration: duration-default (120ms), ease: ease-out

Interaction States

  • Hover: Background fill at duration-fast (80ms) ease-out. Primer's hover states are deliberately subtle — a single token step up on the background scale. No scale, no shadow, no border change. Code blocks and diff lines: 0ms background change.
  • Press/Active: Background steps down at 50ms linear. No transform scale on text buttons or icon buttons.
  • Focus: Focus ring appears at duration-fast (80ms) ease-out. Uses #0969DA (Primer blue) at 2px with 3px offset on light mode, adjusted for dark mode. In keyboard navigation mode, focus ring is always visible — no fade on blur.
  • Loading (inline): Animated spinner — 16px or 24px, 750ms linear rotation, #57606A (muted gray). Skeleton loaders use a minimal shimmer at 1400ms ease-in-out infinite.
  • Status dots (CI checks, deployment status): Pulsing animation only for "in progress" state — scale 1→1.4 at 800ms ease-in-out, infinite. Passed/failed states are static.
  • Merge button (Pull Request): The merge button's state change (pending → merged) is the most visually significant interaction in the product. Button color transitions from green to purple over 200ms ease-out. Confetti SVG animation plays once from the button origin on successful merge — this is the sole expressive animation in functional UI.
  • Reduced motion: All transforms removed. Transitions become opacity fades at 80ms. Confetti animation skipped. CI status pulse stops — static dot only. Octocat/Lottie animations are paused at first frame.

Rules

  • Code is static. Syntax highlighting, diff highlighting, line number changes, and editor state changes happen at 0ms. Animation in code contexts distracts from reading and implies the content is less reliable.
  • Duration caps at 200ms for functional UI. Any transition longer than 200ms is expressive, not functional, and must be justified. The Octocat layer is exempt — it is content, not UI.
  • Hover states must be imperceptible to someone who is not paying attention to them. The subtle tint shift acknowledges interactivity without demanding acknowledgment in return.
  • Always implement prefers-reduced-motion. All transforms are disabled, durations drop to 0ms or 80ms opacity fades, spinners become static indicators, and the merge confetti is suppressed. The CI status pulse stops entirely.

Use this in your agent

The DesignMD MCP server returns this full motion system in one call. Combine it with design tokens using get_full_system.

get_full_system("github")
Set up MCP →