web-tooling-storybook-f750af — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-tooling-storybook-f750af (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: A story is a component in one state, written as a plain object in CSF 3.0: a defaultmetaexport describing the component, then one named export per state. Props come fromargsso the controls panel can edit them,argTypesshapes those controls,tags: ["autodocs"]turns the file into a documentation page, and aplayfunction drives interactions withstorybook/testso the same story doubles as a test.
Detailed Resources:
storybook/test API, file naming, migration notesA story file usually serves both, but the two make different demands on how it is written.
tags: ["autodocs"] generates the page. Follow examples/docs.md.fn() args record calls, and a play function performs the interaction and asserts. Follow examples/testing.md.<critical_requirements>
Write CSF 3.0 with `satisfies Meta<typeof Component>`. The satisfies clause type-checks meta against the component's props while keeping the inference StoryObj<typeof meta> needs, so every story's args are checked against the real prop types.
Give `meta` a `component`. Controls are inferred from its props and autodocs documents it, so a meta without one produces a page with no props table and stories that may not render.
Express state through `args`, not through JSX. The controls panel edits args; props hardcoded in a render are invisible to it, and the story stops being explorable.
Drive interactions from a `play` function using `storybook/test`. Its userEvent reproduces the event sequence a real input produces, and fn() args record calls so the assertion can be about behaviour rather than markup.
Export stories as named exports. The default export is the meta, and a file can only have one.
</critical_requirements>
Auto-detection: Storybook, .stories.tsx, .stories.ts, CSF, Meta, StoryObj, satisfies Meta, args, argTypes, play function, canvasElement, within, autodocs, decorators, parameters, storybook/test, .storybook/main.ts, .storybook/preview.ts
Applies to:
Handled elsewhere:
<philosophy>
Storybook is component-driven development made concrete: build from the bottom up, and let each component exist before the screen that uses it does.
A story is a documented example rather than a test case. It captures one meaningful state — primary, disabled, loading, error — and its value is that a person can look at it. A play function then turns that same example into a test without changing what it documents, which is why the assertion belongs to the story rather than the story being written for the assertion.
</philosophy>
<decision_framework>
Do all the variations come from props?
├─ YES → args only
└─ NO → Children or composition?
├─ Simple (text, one element) → args.children
└─ Complex (several elements) → render: (args) => …
└─ Needs state to demonstrate? → render: function Render(args) { … }
(a named function, so hooks are legal)A render that keeps growing is usually the component's API asking for the composition the render is faking.
Interaction to verify? → play function with storybook/test
Appearance to lock down? → visual regression parameters on the story
Accessibility to check? → a11y parameters; the addon runs axe on every story
Nothing to verify yet? → leave it as a documented statePrimitives document every variant; composed components show the compositions that are actually used; page-level components show the states that are hard to reach by hand — loading, empty, error. A component with no visual output gets none.
</decision_framework>
<patterns>
A typed meta default export, then one named export per state.
const meta = {
title: "Components/Button",
component: Button,
tags: ["autodocs"],
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: { variant: "primary", children: "Save" },
};title decides where the component sits in the sidebar; nested segments ("Components/Forms/Input") create the hierarchy.
Full code: examples/core.md
args are the props a story renders with; argTypes describe the control that edits them. Defaults on meta are inherited, so each story states only what makes it different.
const meta = {
component: Card,
args: { title: "Card title" },
argTypes: {
variant: { control: { type: "select" }, options: ["default", "outline"] },
padding: { control: { type: "range", min: 0, max: 64, step: 4 } },
internalId: { table: { disable: true } },
},
} satisfies Meta<typeof Card>;Args must be serializable — a function arg is fn() from storybook/test, and a non-serializable value such as a React element reaches a control through mapping.
Full code, and the control type per prop type: examples/core.md · reference.md
A decorator wraps the story: providers a component needs, or layout it needs to be visible.
const meta = {
component: Modal,
decorators: [
(Story) => (
<div style={{ padding: "3rem" }}>
<Story />
</div>
),
],
} satisfies Meta<typeof Modal>;Global decorators go in .storybook/preview, component decorators on meta, and story decorators on the story. They run outermost to innermost, so order matters wherever one provider depends on another.
Full code: examples/core.md
Parameters configure Storybook and its addons, at whichever level the setting belongs to — global, component or story.
parameters: {
layout: "centered",
docs: { description: { component: "A modal dialog for confirmations." } },
},A story-level parameter overrides the component's, which overrides the global one.
Full list: reference.md
A play function runs after render, inside the rendered canvas.
export const Submitted: Story = {
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText(/email/i), "[email protected]");
await userEvent.click(canvas.getByRole("button", { name: /sign in/i }));
await expect(args.onSubmit).toHaveBeenCalled();
},
};within(canvasElement) scopes queries to this story rather than the whole page. Every userEvent call is awaited, and a story's play function can be called from another's to build a multi-step flow.
Full code: examples/testing.md
Use render when a story needs more than one element, or state to demonstrate the component.
export const WithItems: Story = {
render: (args) => (
<List {...args}>
{ITEMS.map((item) => (
<ListItem key={item}>{item}</ListItem>
))}
</List>
),
args: { variant: "default" },
};Spread args into the component so the controls still work. A render function needing hooks is written as a named function (render: function Render(args) { … }), since hooks are only legal inside a component.
Full code: examples/core.md
Tags decide where a story appears and what runs against it — autodocs generates the page, dev shows it in the sidebar, test includes it in test runs. Inherited tags are removed with a ! prefix.
export const Experimental: Story = {
tags: ["!test", "experimental"],
};Full code: examples/addons.md
</patterns>
<red_flags>
Breaks at runtime:
meta without component — controls have no props to infer from, autodocs has nothing to document, and the story may not render at alluserEvent calls that are not awaited — the assertion runs before the interaction finishes, so the story fails or passes depending on timingrender function using hooks while written as an arrow expression — hooks need a named function that Storybook can treat as a componentfn(), and elements need mapping, or the control panel cannot round-trip the valueSurprising behaviour:
render instead of passed as args — controls silently do nothing, and the docs page shows a component nobody can experiment withfireEvent in a play function — it dispatches one event where a real interaction produces a sequence, so focus, hover and keyboard behaviour go untestedargTypesRegex) rather than declared with fn() — an implicit action cannot be asserted on inside a play functionplay — they run at module evaluation, before anything is renderedwaitFor or a findBy query rather than a getByargs can appear to have no effect until the page is refreshedtags: ["autodocs"] produces a story with no documentation page and no warning that one was expectedVersion-specific behaviour — deprecated fields, renamed globals, removed packages — is in reference.md rather than here, because which of them applies depends on the major you are on.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.