typescript-variables — Claude Code skill: TypeScript conventions
Applies TypeScript variable conventions: const assertions, enum replacement with literal types, unions over boolean flags, null vs undefined distinction.
npx skills add mkosir/typescript-style-guide --skill typescript-variables -a claude-codeWhat typescript-variables does
The skill reviews and fixes variable declarations following the TypeScript Style Guide. It uses as const for constants so TypeScript preserves exact literal types and catches mutations at compile time. It replaces enum with type literal unions, const assertion arrays for looping, or objects for arbitrary keys — enums increase bundle size and generate runtime objects.
It suggests type unions instead of multiple boolean flags when states are mutually exclusive: instead of isPending, isProcessing, isConfirmed, use a single userStatus: 'pending' | 'processing' | 'confirmed'. It distinguishes null (explicitly empty) from undefined (missing or optional field). TypeScript 5.8 introduced the --erasableSyntaxOnly flag that disables enums at the compiler level — a reason to reconsider their use. The skill reads the repository configuration first and applies only the guidance relevant to the current task.
What it helps with
- replace enums with zero-runtime literal types
- catch constant mutations at compile time
- remove confusion from multiple boolean flags
- distinguish null and undefined by meaning
- narrow types to exact literals
Example prompts for the agent
- check variable declarations in
src/models/user.tsand replace enum with literal type - add
as constto all constants inconfig.ts - replace
isLoading,isError,isSuccessflags with a single union type
How to install typescript-variables
1Skills CLI (Vercel)
npx skills add mkosir/typescript-style-guide --skill typescript-variables -a claude-code2Manually: put the files in place
~/.claude/skills/typescript-variables/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/typescript-variables/
1Skills CLI (Vercel)
npx skills add mkosir/typescript-style-guide --skill typescript-variables -a codex2Manually: put the files in place
.agents/skills/typescript-variables/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add mkosir/typescript-style-guide --skill typescript-variables -a cursor2Manually: put the files in place
.cursor/skills/typescript-variables/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install mkosir/typescript-style-guide typescript-variables2Manually: put the files in place
.github/skills/typescript-variables/SKILL.md1Skills CLI (Vercel)
npx skills add mkosir/typescript-style-guide --skill typescript-variables -a gemini-cli2Platform CLI
gemini skills install https://github.com/mkosir/typescript-style-guide --path skills/typescript-variablesManually: put the files in place
.devin/skills/typescript-variables/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add mkosir/typescript-style-guide --skill typescript-variables -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about typescript-variables
How to install typescript-variables in Claude Code?
Run npx skills add mkosir/typescript-style-guide --skill typescript-variables -a claude-code. The skill will appear in ~/.claude/skills/typescript-variables/SKILL.md.
What does typescript-variables do?
It applies variable conventions: replaces enums with literal types, adds as const to constants, removes boolean flags in favor of type unions, distinguishes null from undefined.
When to use null and when undefined?
Use null for an explicitly empty value (function return, assignment). Use undefined for a missing or optional field (form, request payload, database field).
Source text
SKILL.md · MIT
---
name: typescript-variables
description: Apply, review, and explain TypeScript variable conventions. Use automatically for tasks involving variable declarations, const assertions, enum alternatives, literal-union state modelling, boolean flags, or choosing between null and undefined.
---
# Variables
Apply the TypeScript Style Guide's variable conventions in the context of the current task.
## Workflow
1. Inspect the consuming repository's conventions and configuration.
2. Let explicit repository conventions take precedence over this opinionated guidance.
3. Apply, review, or explain only the guidance relevant to the task.
4. State important tradeoffs when the appropriate choice depends on context or judgment.
## Boundaries
- Keep TypeScript and ESLint responsible for checks they can enforce automatically.
- Do not introduce unrelated TypeScript Style Guide conventions merely because this skill is active.
## Related Guidance
### Application State
For detailed guidance on states that require different data, use `typescript-discriminated-unions` when it is available.
<!-- BEGIN CANONICAL GUIDE CONTENT -->
## Variables
### Const Assertion
Strive to declare constants using the const assertion `as const`:
Constants are used to represent values that are not meant to change, ensuring reliability and consistency in a codebase. Const assertions preserve literal types and infer readonly properties.
- Type Narrowing - Using `as const` ensures that literal values (e.g., numbers, strings) are treated as exact values instead of generalized types like `number` or `string`.
- Readonly Properties - Objects and arrays get readonly properties, so TypeScript catches direct mutations.
Examples:
- Objects
```ts
// ❌ Avoid
const FOO_LOCATION = { x: 50, y: 130 }; // Type { x: number; y: number; }
FOO_LOCATION.x = 10;
// ✅ Use
const FOO_LOCATION = { x: 50, y: 130 } as const; // Type '{ readonly x: 50; readonly y: 130; }'
FOO_LOCATION.x = 10; // Error
```
- Arrays
```ts
// ❌ Avoid
const BAR_LOCATION = [50, 130]; // Type number[]
BAR_LOCATION.push(10);
// ✅ Use
const BAR_LOCATION = [50, 130] as const; // Type 'readonly [50, 130]'
BAR_LOCATION.push(10); // Error
```
- Template Literals
```ts
// ❌ Avoid
const RATE_LIMIT = 25;
const RATE_LIMIT_MESSAGE = `Max number of requests/min is ${RATE_LIMIT}.`; // Type string
// ✅ Use
const RATE_LIMIT = 25;
const RATE_LIMIT_MESSAGE = `Max number of requests/min is ${RATE_LIMIT}.` as const; // Type 'Max number of requests/min is 25.'
```
### Enums & Const Assertion
Enums are discouraged in the TypeScript ecosystem due to their runtime cost and quirks.
The TypeScript documentation outlines several [pitfalls](https://www.typescriptlang.org/docs/handbook/enums.html#const-enum-pitfalls), and TypeScript 5.8 introduced the [--erasableSyntaxOnly](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#the---erasablesyntaxonly-option) flag to disable runtime-generating features like enums altogether.
<Rule href="https://eslint.org/docs/latest/rules/no-restricted-syntax">{`'no-restricted-syntax': [
'error',
{
selector: 'TSEnumDeclaration',
message: 'Replace enum with a literal type or a const assertion.',
},
]`}</Rule>
As a rule of thumb, prefer:
- Literal types whenever possible.
- Const assertion arrays when looping through values.
- Const assertion objects when enumerating arbitrary values.
Examples:
- Use literal types to avoid runtime objects and reduce bundle size.
```ts
// ❌ Avoid using enums as they increase the bundle size
enum UserRole {
GUEST = 'guest',
MODERATOR = 'moderator',
ADMINISTRATOR = 'administrator',
}
// Transpiled JavaScript
('use strict');
var UserRole;
(function (UserRole) {
UserRole['GUEST'] = 'guest';
UserRole['MODERATOR'] = 'moderator';
UserRole['ADMINISTRATOR'] = 'administrator';
})(UserRole || (UserRole = {}));
// ✅ Use literal types - Types are stripped during transpilation
type UserRole = 'guest' | 'moderator' | 'administrator';
const isGuest = (role: UserRole) => role === 'guest';
```
- Use const assertion arrays when looping through values.
```tsx
// ❌ Avoid using enums
enum USER_ROLES {
guest = 'guest',
moderator = 'moderator',
administrator = 'administrator',
}
// ✅ Use const assertions arrays
const USER_ROLES = ['guest', 'moderator', 'administrator'] as const;
type UserRole = (typeof USER_ROLES)[number];
const seedDatabase = () => {
USER_ROLES.forEach((role) => {
db.roles.insert(role);
}
}
const insert = (role: UserRole) => {...
const UsersRoleList = () => {
return (
<div>
{USER_ROLES.map((role) => (
<Item key={role} role={role} />
))}
</div>
);
};
const Item = ({ role }: { role: UserRole }) => {...
```
- Use const assertion objects when enumerating arbitrary values.
```ts
// ❌ Avoid using enums
enum COLORS {
primary = '#B33930',
secondary = '#113A5C',
brand = '#9C0E7D',
}
// ✅ Use const assertions objects
const COLORS = {
primary: '#B33930',
secondary: '#113A5C',
brand: '#9C0E7D',
} as const;
type Colors = typeof COLORS;
type ColorKey = keyof Colors; // Type "primary" | "secondary" | "brand"
type ColorValue = Colors[ColorKey]; // Type "#B33930" | "#113A5C" | "#9C0E7D"
const setColor = (color: ColorValue) => {...
setColor(COLORS.primary);
setColor('#B33930');
```
### Type Union & Boolean Flags
Embrace type unions, especially when type union options are mutually exclusive, instead multiple boolean flag variables.
Boolean flags have a tendency to accumulate over time, leading to confusing and error-prone code, since they hide the actual app state.
```ts
// ❌ Avoid introducing multiple boolean flag variables
const isPending, isProcessing, isConfirmed, isExpired;
// ✅ Use type union variable
type UserStatus = 'pending' | 'processing' | 'confirmed' | 'expired';
const userStatus: UserStatus;
```
Use a literal union when only the state value changes. When each state requires different data, use a [discriminated union](#application-state) to represent the valid states explicitly.
### Null & Undefined
With `strictNullChecks`, `null` and `undefined` have distinct types and meanings. Use them consistently based on what absence means in the application.
Strive to:
- Use `null` when a value is explicitly empty, such as an assignment or function return value.
- Use `undefined` when a value is missing or omitted, such as an optional field in a form, request payload, or database query ([Prisma differentiation](https://www.prisma.io/docs/concepts/components/prisma-client/null-and-undefined)).
<!-- END CANONICAL GUIDE CONTENT -->
© mkosir · MIT · mkosir/typescript-style-guide