TL;DR A skill stops being a fixed script the moment you pass it arguments. Declare
arguments: name typein the frontmatter and use$name/$typein the body (the most readable way); or use 0-based positionals ($0first,$1second) or$ARGUMENTS(the whole string).argument-hintautocompletes them, and editing theSKILL.mdtext is picked up live.
You write a skill to review a component and it works. Then you want almost the same one for another case and you end up duplicating the SKILL.md. You don't have to: a skill can take arguments and behave differently on every call. That's the jump from fixed command to template.
What you see
/component SearchBar form
└ $name └ $type
→ "Create a Vue component named SearchBar, of type form…"
The three ways to take arguments
1. $ARGUMENTS — everything you type
The simplest: $ARGUMENTS is replaced by the whole string after the command. If you don't put it in the body, Claude Code appends it anyway as ARGUMENTS: <value>, so it's never lost.
2. Positionals $0, $1 — heads up, they're 0-based
$0 is the first argument, $1 is the second (they map to $ARGUMENTS[0] and $ARGUMENTS[1]). The classic slip is expecting $1 to be the first: it isn't. For values with spaces, quote them: /skill "hello world" second gives $0 = hello world and $1 = second.
3. Named arguments — the most readable
Declare the names in the frontmatter and use them by name. It's self-documenting and survives reordering the text:
---
name: component
description: Scaffold a Vue component with its tests.
argument-hint: [name] [type]
arguments: name type
disable-model-invocation: true
---
Create a Vue 3 component named `$name`, of type `$type`.
- Clear structure (logic, template, styles).
- Typed props with sensible defaults.
- One test covering the main case.
Invoke it:
/component SearchBar form
$name → SearchBar, $type → form. The names map to the positions in order.
Reference
| Substitution | What it inserts |
|---|---|
$ARGUMENTS |
Everything you type after the command |
$ARGUMENTS[0] / $0 |
The first argument (0-based) |
$ARGUMENTS[1] / $1 |
The second argument |
$name |
The argument named in arguments:, by position |
| Frontmatter | What it does |
|---|---|
arguments |
Names the positionals so you can use $name in the body (string or YAML list) |
argument-hint |
Autocomplete hint: [name] [type] |
disable-model-invocation |
true so only you can invoke it (usual for a command with args) |
Real gotchas:
- 0-based: if you want the first argument, it's
$0, not$1. - Literal
$: escape it with a backslash (\$1.00) so it isn't treated as an argument. - Live edits: changing the
SKILL.mdtext is picked up without a restart; only a plugin'shooks/,.mcp.json, oragents/need/reload-plugins.
This builds on Create reusable commands with skills: there you write the SKILL.md, here you turn it into a template you fill per call. And remember that custom commands and skills are now the same thing: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create the same /deploy.
Official docs: Extend Claude with skills
Related: Create reusable commands with skills
Requirements
- A Claude Code skill (
~/.claude/skills/<name>/SKILL.mdto have it across all your projects).