skills/typescript-satisfies-operator/SKILL.md
Guides proper usage of TypeScript's satisfies operator vs type annotations. Use this skill when deciding between type annotations (colon) and satisfies, validating object shapes while preserving literal types, or troubleshooting type inference issues.
npx skillsauth add Chris-Maskey/opencode-config typescript-satisfies-operatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
satisfies OperatorThe satisfies operator validates that an expression matches a type without changing the inferred type. This is different from type annotations (:) which widen the type.
Key insight from Matt Pocock:
satisfies, the value BEATS the type"type RoutingPathname = "/products" | "/cart" | "/checkout";
// Type annotation - widens to union
const url1: RoutingPathname = "/products";
// url1 is typed as: RoutingPathname (wide)
// Satisfies - keeps literal
const url2 = "/products" satisfies RoutingPathname;
// url2 is typed as: '/products' (narrow)
// Why it matters:
const test1: "/products" = url1; // Error: RoutingPathname not assignable to '/products'
const test2: "/products" = url2; // Works
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
// Type annotation loses specific property types
const palette1: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
};
palette1.green.toUpperCase(); // Error: 'toUpperCase' doesn't exist on string | RGB
// Satisfies validates AND preserves literal types
const palette2 = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255], // Error: Typo caught!
} satisfies Record<Colors, string | RGB>;
palette2.green.toUpperCase(); // Works - green is inferred as string
| Annotation Style | Type vs Value | Use Case |
| ---------------- | ------------- | ---------------------------------- |
| : Type (colon) | Type wins | Need wider type for reassignment |
| satisfies Type | Value wins | Need validation + narrow inference |
| as Type | Lies to TS | Escape hatch (use sparingly!) |
| No annotation | Inference | Most common - let TS infer |
Use satisfies when:
Use colon annotation when:
as const satisfiesCombine as const for immutability with satisfies for validation:
const routes = {
home: "/",
products: "/products",
cart: "/cart",
} as const satisfies Record<string, string>;
// routes.home is typed as '/' (readonly literal)
// But validated against Record<string, string>
as const satisfies Over Type AnnotationWhen you need both validation AND literal type preservation:
// Bad - type annotation widens types, loses literals
const LANG_MAP: Record<string, string> = {
en: '1',
cs: '2',
} as const;
// LANG_MAP.en is just string, not '1'
// Good - satisfies validates while preserving literal types
const LANG_MAP = {
en: '1',
cs: '2',
} as const satisfies Record<string, string>;
// LANG_MAP.en is '1' (narrow literal type)
type Locale = 'en' | 'cs';
// Validates all locales are present, preserves specific values
const SHOP_GRAPHQL_LOCALE_LANGUAGE_ID_MAP = {
en: '1',
cs: '2',
} as const satisfies Record<Locale, string>;
// TypeScript will error if you miss a locale:
const INCOMPLETE_MAP = {
en: '1',
// cs: '2', // Error: Property 'cs' is missing
} as const satisfies Record<Locale, string>;
type Config = {
api: string;
timeout: number;
retries: number;
};
// Validates shape, but keeps literal types for autocomplete
const config = {
api: "https://api.example.com",
timeout: 5000,
retries: 3,
} satisfies Config;
// config.api is 'https://api.example.com', not string
type EventMap = Record<string, (...args: unknown[]) => void>;
const handlers = {
click: (x: number, y: number) => console.log(x, y),
submit: (data: FormData) => console.log(data),
} satisfies EventMap;
// handlers.click is (x: number, y: number) => void
// Not (...args: unknown[]) => void
type Status = "pending" | "approved" | "rejected";
const statusLabels = {
pending: "Waiting for review",
approved: "Approved",
rejected: "Rejected",
} satisfies Record<Status, string>;
// If you add a new Status, TypeScript will error until you add it here
tools
Anti-patterns and mistakes to avoid as a product manager. Use when evaluating leadership behaviors, improving team dynamics, reflecting on management practices, or onboarding new product managers.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
testing
Design effective CTAs using visual attention and gaze psychology principles. Use when designing landing pages, button hierarchies, conversion elements, or optimizing user attention flow through interfaces.
tools
Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include "Vercel Sandbox browser", "microVM Chrome", "agent-browser in sandbox", "browser automation on Vercel", or any task requiring Chrome in a Vercel Sandbox.