Instagram Brand Manual

Foundations

Dark Mode

Every themed --ig-* token is declared twice in the captured bundle, once in a light theme scope and once in a dark one. The brand gradient, fixed semantic colours, and photography do not change between themes.

Theme scopes

What the two theme scopes declare

The captured bundle declares each themed token in a light scope (._aa4c) and a dark scope (._aa4d). It records values, not the reasoning behind them.

Pure white and black in light mode

The light scope declares --ig-primary-background as 255, 255, 255 and --ig-primary-text as 0, 0, 0.

Dark scope values

The dark scope declares --ig-primary-background as 12, 16, 20 and --ig-primary-text as 245, 245, 245.

Surface steps

Light mode: primary and elevated backgrounds are both 255, 255, 255; secondary is 243, 245, 247. Dark mode: primary 12, 16, 20, elevated 33, 35, 40, secondary 37, 41, 46.

Brand stays constant

The five-stop hero gradient, error/success colours, and all photography are identical in both modes. Only neutral UI chrome — backgrounds, text, borders — is themed.

Token pairs

Light → dark token values

Every themed token is defined twice: once under :root (light, default) and once under [data-theme="dark"]. The pairing below shows the confirmed values and why each dark variant isn't a simple inversion.

TokenLightDarkWhy
--ig-primary-background255, 255, 25512, 16, 20Light scope declares pure white; dark scope declares 12, 16, 20
--ig-secondary-background243, 245, 24737, 41, 46Secondary surfaces such as input fills and chips
--ig-elevated-background255, 255, 25533, 35, 40Cards, modals, sheets
--ig-primary-text0, 0, 0245, 245, 245Light scope declares pure black; dark scope declares 245, 245, 245
--ig-secondary-text115, 115, 115168, 168, 168Lighter grey in the dark scope
--ig-separator219, 219, 21938, 38, 38Same role in both scopes
--ig-stroke219, 219, 21985, 85, 85Brighter than separator — used where a border needs to be more visible (input outlines)
--ig-hover-overlay-alpha0.050.1Doubled in dark mode — a 5% white overlay is nearly invisible on dark surfaces, so dark mode compensates with higher alpha

What never changes

Theme-independent tokens

Some tokens are deliberately excluded from theming. These represent the system's fixed brand identity and semantic meanings that must stay legible and consistent regardless of theme.

--ig-stop-rose / magenta / purple / orange / yellow

The five brand gradient stops are identical in both modes — the gradient is a fixed brand asset, not a themed surface.

--ig-error, --ig-success, --ig-live-badge

Semantic status colours stay fixed — a red error needs to read as urgent in both themes without renegotiating meaning.

--ig-always-white / --ig-always-black

Named explicitly as theme-independent — used for text/icons over photos and gradients where the surface itself doesn't change with theme.

Photography & user media

Photos and videos are never recoloured or filtered for dark mode. Only UI chrome adapts.

Implementation

The data-theme attribute pattern

Theme state lives as a data attribute on the root html element, toggled by a small client component and persisted to localStorage. Every themed token resolves through this single attribute — no component needs its own theme-awareness.

globals.css — token definition pattern
:root {
  --ig-primary-background: 255, 255, 255;
  --ig-primary-text: 0, 0, 0;
  /* ...every themed token defined once, light values */
}

[data-theme="dark"] {
  --ig-primary-background: 12, 16, 20;
  --ig-primary-text: 245, 245, 245;
  /* ...same token names, dark values only */
}

/* Usage anywhere in the system: */
.surface {
  background: rgb(var(--ig-primary-background));
  color: rgb(var(--ig-primary-text));
}
Theme toggle — client component pattern
"use client";
import { useEffect, useState } from "react";

export default function ThemeToggle() {
  const [theme, setTheme] = useState(null);

  useEffect(() => {
    setTheme(document.documentElement.getAttribute("data-theme") || "light");
  }, []);

  function toggle() {
    const next = theme === "dark" ? "light" : "dark";
    document.documentElement.setAttribute("data-theme", next);
    window.localStorage.setItem("ig-theme", next);
    setTheme(next);
  }

  return (
    <button onClick={toggle} aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}>
      {theme === "dark" ? "☀️" : "🌙"}
    </button>
  );
}
Inline script — prevents flash of wrong theme on load
// Runs before paint, in <head>, reads saved preference or system setting
(function () {
  var saved = localStorage.getItem("ig-theme");
  var theme = saved || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
  document.documentElement.setAttribute("data-theme", theme);
})();

Do

  • Use semantic tokens exclusively — Never reference rgb(255,255,255) directly — always rgb(var(--ig-primary-background)). This is the only mechanism that makes dark mode automatic.
  • Test elevation in dark mode specifically — Shadows are far less visible on dark backgrounds. Verify that cards and modals still read as elevated using the Elevation page's --shadow-* tokens, which compensate with adjusted opacity.
  • Read the system preference on first load — Default to prefers-color-scheme when no saved preference exists — don't force light mode on users whose OS is set to dark.
  • Inline the theme-detection script in <head> — Without this, the page flashes light mode before JS hydrates and corrects it — a jarring 'flash of wrong theme'.

Don’t

  • Don't invert photography or video — User media is never recoloured, filtered, or inverted for dark mode — only UI chrome is themed.
  • Don't hardcode surface or text values — Use the captured tokens. Light mode resolves to pure white surfaces and pure black text; the dark scope resolves to rgb(12,16,20) and rgb(245,245,245).
  • Don't theme the brand gradient — The five-stop hero gradient is identical in both modes — it's a fixed brand asset, not a themed surface.
  • Don't hardcode a single hover-overlay alpha — Dark mode doubles the hover overlay alpha (0.05 → 0.1) because the same overlay is far less visible against dark surfaces.