Literal Types
String, number, and boolean literal types — model fixed variants, narrow with equality, and combine with unions for safe UI props.
- typescript
- literal-types
A literal type is a type that is exactly one value — not any string, but specifically 'loading'. Literal unions are how TypeScript models buttons, tabs, HTTP methods, and feature flags without full enums.
type Align = 'left' | 'center' | 'right';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Yes = true; // boolean literal
Docs: Literal Types, Narrowing.
Why literals beat bare string
function setVariant(v: string) {
/* accepts typos */
}
function setVariantSafe(v: 'primary' | 'secondary' | 'danger') {
/* typos fail compile */
}
UI APIs should fail at compile time for 'primry'.
Inference and widening
let status = 'idle'; // string — widened
const statusConst = 'idle'; // 'idle' — literal
let flagged = true; // boolean
const flaggedConst = true; // true
let often widens; const keeps literals for primitives. For objects:
const config = { mode: 'dark' };
// mode: string
const configLit = { mode: 'dark' } as const;
// mode: 'dark'
See readonly and const assertions.
Narrowing with equality
type Status = 'idle' | 'loading' | 'error';
function message(s: Status) {
if (s === 'error') {
return 'Something failed';
}
return s === 'loading' ? '…' : 'Ready';
}
Discriminated unions are literal tags on objects — discriminated unions for UI state.
Template literal types (preview)
type EventName = `on${Capitalize<'click' | 'focus'>}`;
// 'onClick' | 'onFocus'
Useful for typed event maps and CSS-in-JS. Deep dive: template literal types.
React props
type ButtonProps = {
size?: 'sm' | 'md' | 'lg';
type?: 'button' | 'submit' | 'reset';
children: React.ReactNode;
};
function Button({ size = 'md', type = 'button', children }: ButtonProps) {
return (
<button type={type} data-size={size}>
{children}
</button>
);
}
Prefer literal unions for design tokens over freeform strings.
as const satisfies pattern
const routes = {
home: '/',
about: '/about',
} as const;
type Path = (typeof routes)[keyof typeof routes]; // '/' | '/about'
Or with satisfies for checking against a wider type while keeping literals — satisfies operator.
Numeric and boolean literals
type Port = 80 | 443 | 3000;
type Feature = { enabled: true; flag: string } | { enabled: false };
function run(f: Feature) {
if (f.enabled) {
console.log(f.flag);
}
}
Boolean discriminants work; string discriminants are often clearer in logs.
Footguns
- Union with
string—'a' | 'b' | stringcollapses tostring. - Over-using literals for open sets — user-entered names stay
string. - Comparing without narrowing — still need runtime checks for external data.
- Enum vs union — enums vs union types.
// Collapsed
type Oops = 'GET' | 'POST' | string; // string
Interview out-loud answer
“Literal types fix a type to a specific value. Literal unions model closed variants for props and state. const and as const preserve literals; let widens. I use them for UI variants and discriminants, not for freeform user text.”
Related on this site
- Enums vs union types
- Discriminated unions for UI state
- Readonly and const assertions
- Template literal types
- satisfies operator
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.