keyof typeof and Indexed Access
Derive keys and value types from objects with keyof, typeof, and T[K] — the toolkit behind typed forms, configs, and safe property access.
- typescript
- keyof-typeof
Three operators show up constantly in real TypeScript:
typeof(type query) — type from a valuekeyof— union of keys of a type- Indexed access
T[K]— type of propertyKonT
Together they keep configs, forms, and dictionaries typed without duplicating lists of keys.
Docs: typeof, keyof, Indexed Access.
typeof in type position
const theme = {
bg: '#0b0b0b',
fg: '#f5f5f5',
accent: '#6ee7b7',
} as const;
type Theme = typeof theme;
// { readonly bg: '#0b0b0b'; readonly fg: '#f5f5f5'; readonly accent: '#6ee7b7' }
Value space typeof (JS) and type space typeof (TS) share a keyword; context decides.
keyof
type ThemeKey = keyof Theme; // 'bg' | 'fg' | 'accent'
function getToken(key: ThemeKey) {
return theme[key];
}
Without keyof, people type key: string and lose safety.
Indexed access
type Accent = Theme['accent']; // '#6ee7b7'
type AnyColor = Theme[keyof Theme]; // union of color literals
type User = { id: string; age: number; active: boolean };
type UserId = User['id']; // string
type UserField = User[keyof User]; // string | number | boolean
The golden helper: getProp
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const age = getProp({ id: '1', age: 42 }, 'age'); // number
Config maps and routes
const routes = {
home: '/',
user: '/users/:id',
settings: '/settings',
} as const;
type RouteName = keyof typeof routes;
type RoutePath = (typeof routes)[RouteName];
function href(name: RouteName): string {
return routes[name];
}
Forms: field errors typed to fields
type FormValues = {
email: string;
password: string;
};
type FormErrors = Partial<Record<keyof FormValues, string>>;
const errors: FormErrors = {
email: 'Required',
// username: '…' // error — not a key
};
See Record and Partial patterns for forms.
keyof and index signatures
type Dict = { [key: string]: number };
type K = keyof Dict; // string | number (historical number key coercion)
For stricter keys, prefer unions:
type Strict = Record<'a' | 'b', number>;
type SK = keyof Strict; // 'a' | 'b'
typeof on functions and classes
function createUser(name: string) {
return { id: crypto.randomUUID(), name };
}
type CreateUser = typeof createUser;
type User = ReturnType<CreateUser>;
class Store {
value = 0;
}
type StoreInstance = InstanceType<typeof Store>;
Mapped types connection
type OptionalFlags<T> = {
[K in keyof T]?: boolean;
};
type UserFlags = OptionalFlags<User>;
// { id?: boolean; age?: boolean; active?: boolean }
See mapped types intro.
Footguns
| Footgun | Detail |
|---|---|
Forgetting as const |
Keys widen; literals become string |
keyof on arrays |
Includes number + array methods if not careful |
Using JS typeof null |
Returns 'object' — different problem |
obj[key] with key: string |
Needs index signature or cast |
const keys = Object.keys(theme); // string[] — not (keyof Theme)[]
// assert or validate when mapping
(Object.keys(theme) as Array<keyof typeof theme>).forEach((k) => {
console.log(theme[k]);
});
Interview out-loud answer
“typeof pulls a type from a value, keyof builds a key union, and T[K] reads a property type. Together they type safe accessors and configs derived from a single source of truth. K extends keyof T is the standard constraint.”
Related on this site
- Generics basics
- Generic constraints
- Mapped types intro
- Readonly and const assertions
- Record and Partial patterns for forms
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.