Organizes ds-react component file layouts into a standard, modular structure without breaking public APIs.
Install
mkdir -p .claude/skills/ds-component-restructure && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/18493" && unzip -o skill.zip -d .claude/skills/ds-component-restructure && rm skill.zipInstalls to .claude/skills/ds-component-restructure
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Restructure a ds-react component's files into canonical folder/naming conventions. Use when reorganizing a component's file layout, splitting a monolithic component file, or renaming files to match domain conventions.Key capabilities
- →Reorganize component files into canonical folder structures
- →Split monolithic component files into smaller, organized units
- →Rename files to match domain-specific conventions
- →Map old file paths to new target structures
- →Update import paths after file restructuring
- →Handle case-only renames safely
How it works
This skill outlines a procedure to restructure `ds-react` component files into a canonical folder and naming convention, ensuring public API compatibility.
Inputs & outputs
When to use ds-component-restructure
- →Splitting a monolithic component file
- →Reorganizing component folder structures
- →Applying canonical naming to existing components
About this skill
ds-component-restructure
Restructure a ds-react component folder to follow canonical conventions — without breaking the public API.
Canonical Structure
<component>/
├── index.ts # "use client" + re-exports from root
├── <Component>.stories.tsx # Storybook stories
├── <Component>.tests.stories.tsx # Storybook play-tests (only if play tests exist)
├── <Component>.meta.ts # Meta info for component. Used for documentation generation.
├── root/
│ ├── <Component>Root.tsx # Main compound component + sub-component namespace
│ ├── <Component>Root.context.ts # Context (if shared across sub-components)
│ ├── <Component>Root.test.ts # Unit tests for root (if needed)
│ └── use<Hook>.ts # Files for root-level hooks (if needed)
├── <subcomponent>/ # One dir per sub-component
│ ├── <Component><Subcomponent>.tsx
│ ├── <Component><Subcomponent>.test.ts # Unit tests for sub-component (if needed)
│ └── use<Hook>.ts # Files for sub-component-level hooks (if needed)
├── helpers/
│ ├── <helper-name>.ts
│ └── <helper-name>.test.ts # Unit tests co-located with helper
└── <Component>.types.ts # Shared types (if needed across multiple files)
Naming Rules
| What | Pattern | Example |
|---|---|---|
| Root component file | <Component>Root.tsx | AccordionRoot.tsx |
| Sub-component file | <Component><Subcomponent>.tsx | AccordionItem.tsx |
| Internal sub-component | <Component><Subcomponent>Internal.tsx | DialogBackdropInternal.tsx |
| Sub-component dir | kebab-case sub-component name | item/ |
| Context file | <Component>Root.context.ts | AccordionRoot.context.ts |
| Shared types file | <Component>.types.ts | Accordion.types.ts |
| Stories | <Component>.stories.tsx | Accordion.stories.tsx |
| Play-test stories | <Component>.tests.stories.tsx | Accordion.tests.stories.tsx |
| Unit test file | <filename>.test.ts(x) | useAccordion.test.ts |
| Meta file | <Component>.meta.ts | Accordion.meta.ts |
index.ts | always index.ts | index.ts |
Procedure
1. Read the Component
Read every file in the component folder — index.ts, all .tsx/.ts source files, meta file, stories, and test stories. Ignore inconsistencies in file naming and structure; the goal is to fix those, not preserve them. Use the component-field in the meta file to determine the proper naming if unsure.
Done when: every file's role (component, hook, type, context, test, metadata) is known, and every symbol currently exported from index.ts is recorded.
2. Map Files to Target Structure
Build this table before touching any files:
| Old path | New path | Renamed? | Publicly exported? | Importers to update |
|---|---|---|---|---|
DialogTrigger.tsx | trigger/DialogTrigger.tsx | No | Yes | root/DialogRoot.tsx |
| ... |
For each file:
- Determine new path and whether it moves/renames
- Check whether content needs splitting (e.g. types →
<Component>.types.ts) - List every file that imports it (grep for the filename or exported symbol)
- Flag circular dep risks: context importing sub-components, helpers importing component files, types importing non-types
Resolve circular deps before proceeding: move shared pieces to a lower-level file (types → .types.ts, utilities → helpers/).
Done when: every file in the component folder has a row in the table, and every circular-dependency risk is resolved or explicitly noted.
3. Create New Files
For each row in the mapping table where the path changes:
- Create the file at the new path with all import paths updated to reflect new locations
- Update every file in the "Importers to update" column — use the mapping table, don't rely on memory
- Do NOT delete old files yet
Case-only renames (path differs only in letter casing, e.g.
heading.stories.tsx→Heading.stories.tsx) must NOT use the create-new + delete-old flow. On case-insensitive filesystems (macOS and Windows default) the two paths resolve to the same file, so creating the new file overwrites the old and the later "delete old" step destroys your content — and git records no rename. Handle these with the two-stepgit mvin the Case-only renames edge case instead.
4. Update Stories
Move stories to component root dir. If stories import from internal paths, update those paths.
Exception: A stories/ subdirectory is acceptable if it already exists — do not flatten it.
Casing: Story filenames follow <Component>.stories.tsx (PascalCase). When the existing file is lowercase (e.g. heading.stories.tsx), this is a case-only rename — follow the Case-only renames procedure, do not create + delete.
5. Update index.ts
All exports are named — no default exports anywhere. The component index.ts re-exports the compound component, its sub-components, and their types from the root file:
"use client";
export {
<Component>, // compound root (named)
<Component>Trigger, // sub-components (named)
// ...
} from "./root/<Component>Root";
export type {
<Component>Props,
<Component>TriggerProps,
// ...
} from "./root/<Component>Root";
Group values first, then types. If types live in <Component>.types.ts, re-export them from there.
Compound root file export pattern
In <Component>Root.tsx, assemble the compound component with Object.assign — do not use an as <Component>Component cast or a dedicated <Component>Component interface:
const <Component>Root = forwardRef<HTMLDivElement, <Component>Props>((props, ref) => {
// ...
});
/**
* Component-level JSDoc (description, `@see`, `@example`) goes directly
* above the `Object.assign` — this is the exported `<Component>` value.
*/
const <Component> = Object.assign(<Component>Root, {
/**
* @see 🏷️ {@link <Component>TriggerProps}
*/
Trigger: <Component>Trigger,
// ...
});
export { <Component>, <Component>Trigger };
export type { <Component>Props, <Component>TriggerProps };
Why Object.assign instead of as: the cast types the const as an interface that has no runtime valueDeclaration, so react-docgen-typescript (used by yarn docgen:meta) fails to extract props and can't document the component when it is exported via a grouped export { } statement. Object.assign lets TypeScript infer the intersection type from the value, so metadata generation works with named, bottom-of-file exports.
Sub-component files follow the same rule — named value export plus named type export at the bottom, no inline export interface and no default:
interface <Component>TriggerProps extends React.HTMLAttributes<HTMLDivElement> {
// ...
}
const <Component>Trigger = forwardRef<HTMLDivElement, <Component>TriggerProps>(/* ... */);
export { <Component>Trigger };
export type { <Component>TriggerProps };
6. Update Meta File
Update imports in <Component>.meta.ts to reflect new paths. If the meta file imports from the component's index.ts, no changes are needed.
If component has no meta file, create one in the component root with the following content based on existing patterns.
Note that each standalone component should have a meta file. But a "sub-component" (like AccordionItem) does not need a meta file.
### 7. Validate No Breaking Changes
**Export diff.** Before deleting old files, compare the exported names recorded during Step 1 against the new `index.ts`. Every name must be present:
- Exports in new index.ts that are missing from original → regression
- Exports in original that are missing from new index.ts → missing export (breaking change)
Concretely: grep for `export` lines in both versions and diff them. All component names and type names must match exactly.
**Build.** Run `yarn workspace @navikt/ds-react build` — must succeed with no TypeScript errors.
**Package root.** Verify `@navikt/core/react/src/index.ts` still re-exports the component. No changes needed there unless the component's `index.ts` path changed (it shouldn't).
**No case-duplicate tracked paths.** This is the most common restructure bug. On case-insensitive filesystems (macOS, Windows) git can end up tracking **two** entries that differ only in casing (e.g. `accordion.stories.tsx` **and** `Accordion.stories.tsx`) while the working tree shows only one file. The stale entry then breaks tests and checkout on other machines. `ls` / file search will NOT reveal it — you must ask git. Run:
```sh
git ls-files | awk '{l=tolower($0); if(seen[l]++) print "CASE-DUP:", $0}'
```
Any output is a leftover. Remove the stale entry from the index by its **exact old path** (this does not touch the correctly-cased file on disk):
```sh
git rm --cached '<exact old lowercase path>'
```
Then confirm only the intended casing remains, e.g. `git ls-files '*<component>*stories*'`.
**Done when:** the export diff is empty, the build succeeds, the package-root re-export is verified, and `git ls-files` reports no case-duplicate paths.
### 8. Remove Old Files
Only after verifying new files are correct and `in
---
*Content truncated.*
When not to use it
- →When breaking changes to the public API are acceptable
- →When default exports are preferred over named exports
- →When React 18/19-only APIs are required
Limitations
- →No breaking changes to public export names and prop shapes
- →Only named exports are allowed
- →Must be React 17 compatible
How it compares
This skill provides a structured, step-by-step procedure for component restructuring, ensuring adherence to specific naming rules and public API preservation, unlike manual refactoring.
Compared to similar skills
ds-component-restructure side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| ds-component-restructure (this skill) | 0 | 17d | No flags | Intermediate |
| react-modernization | 21 | 2mo | No flags | Advanced |
| state-management | 1 | 5mo | No flags | Beginner |
| writing-react-effects | 1 | 3mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by navikt
View all by navikt →You might also like
react-modernization
wshobson
Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.
state-management
redpanda-data
Manage client and server state with Zustand stores and React Query patterns.
writing-react-effects
dust-tt
Writes React components without unnecessary useEffect. Use when creating/reviewing React components, refactoring effects, or when code uses useEffect to transform data or handle events.
form-refactorer
redpanda-data
Refactor legacy forms to use modern Redpanda UI Registry Field components with react-hook-form and Zod validation. Use when user requests: (1) Form refactoring or modernization, (2) Converting Chakra UI or @redpanda-data/ui forms, (3) Updating forms to use Field components, (4) Migrating from legacy form patterns, (5) Implementing forms with react-hook-form and Zod validation.
building-compound-components
tambo-ai
Creates unstyled compound components that separate business logic from styles. Use when building headless UI primitives, creating component libraries, implementing Radix-style namespaced components, or when the user mentions "compound components", "headless", "unstyled", "primitives", or "render props".
rendering-conditional-render
TheOrcDev
Use explicit ternary operators instead of && for conditional rendering. Apply when rendering values that could be 0, NaN, or other falsy values that might render unexpectedly.