# The ds.css package ships a committed dist folder, and its build step is a file copy

> spiritov/ds.css is a small CSS framework that recreates the look of the Nintendo DS interface, published as a prebuilt stylesheet plus ES module widgets. Two of its three published entry points point into dist, and its two license declarations disagree.

**spiritov/ds.css** — css framework recreating the DS / DS Lite's UI

- Repository: https://github.com/spiritov/ds.css
- Website: https://css.ds.dreamyard.xyz
- Stars: 608 · Forks: 10
- Language: CSS
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/spiritov-ds-css

## The npm package is a committed dist folder, and the build only copies files

The manifest publishes exactly one path, the dist directory, and points its main field at a stylesheet inside it. The same directory is committed to the repository, so what you install and what you would see on the default branch are the same bytes rather than a build you make yourself. What the build does is short:
```
postcss ./src/dev.css --config ./postcss/default -o ./src/ds.css
postcss ./src/dev.css --config ./postcss/prefix -o ./src/ds-scoped.css
cp -r ./src/assets/* ./dist/assets && cp ./src/widgets/* ./dist/widgets && cp ./src/ds.css ./src/ds-scoped.css ./dist
```
Two passes over one source file, then a copy. Two things follow. The copy step is written with a shell copy command, so building on Windows needs a POSIX shell rather than a command prompt. And the manifest declares only build scripts, with no test and no lint entry, so nothing in the package checks that the generated stylesheet still matches the source it came from.

## The scoped stylesheet is generated by prefix-wrapping the same source

Both stylesheets come from one file, src/dev.css, compiled twice with different PostCSS configurations. The default configuration produces the plain stylesheet; the configuration named prefix produces the scoped one, and the dependency that does the work is a prefix-wrapping plugin. That is the whole mechanism behind scoped styles: the second stylesheet is not a separate design, it is the first one with its selectors wrapped so they only apply inside a container. The container is a single class name on a parent element, and the documented effect is that a button inside the wrapper picks up the styling while a button outside it stays plain. Worth knowing before editing: because the scoped file is generated, a fix applied directly to it disappears the next time the build runs, and the pinned version of the wrapping plugin is what makes the output stable.

## The repository says MIT, the package manifest says ISC

The two published surfaces declare different licenses. The repository's own metadata records MIT, while the package manifest that npm reads declares ISC, and a license file sits at the root of the tree alongside a custom domain file for the preview site. Both are permissive and both allow reuse without a copyleft obligation, so this is unlikely to block anyone, but they are different texts and a consumer has to pick one surface to believe. The same split shows up in the one-line description: the repository summary says the framework recreates the interface of both the DS and DS Lite, while the manifest description mentions DS Lite only. For a package whose entire purpose is to look like a specific piece of hardware, the difference between the two lines is small but it is the kind of thing worth knowing before you file an issue against the wrong description.

## The unpkg links disagree about whether the path is required

Three installation routes are offered and they are not written the same way. The fastest is a stylesheet link pointing at the bare package name on unpkg, with no file path at all. The scoped stylesheet and the calendar widget are both linked with the dist directory spelled out in full. The reason the first one can omit the path is that unpkg resolves a bare package through its manifest, and that manifest's main field points at the stylesheet inside dist. So one of the three documented URLs works by way of a field in the manifest, and the other two work by spelling out the layout of the package. That is harmless in practice, and it does mean the two styles are not interchangeable: a framework that resolves the bare name for the main file but expects you to know the scoped filename and the widget path will only agree with this particular package's layout.

## The widget is a custom element, and the Svelte path defines it after mount

The included components are ES modules that register custom elements, one of which is a calendar loaded with a module script and used by writing the element name in your markup. It can come from unpkg, from a copy under the dist directory of the repository, or from an installed package, and the framework example does it with a dynamic import inside a lifecycle hook rather than at the top of the module. That choice has a consequence the example does not mention: an element that has not been defined yet is inert, so anything that renders before the import resolves sees an empty tag. It also means the component is client-side only in that pattern, with nothing for a server-rendered pass to emit. The manual route, a plain module script in the document head, avoids the timing question entirely at the cost of not being framework-aware.

## Two roadmap items, both ending in a question mark

The project's own to-do list has two entries, more of one internal component family and extra inspired components, and both are written with a question mark attached rather than as commitments. That is a small signal about where the project is: the recreation of the existing interface is the finished part, and the extension beyond it is still being argued about. The rest of the layout matches a small, mostly static site, with a source directory, a generated dist directory, the PostCSS configuration directory, a lockfile, and a custom domain file that pins the preview site to its own hostname. Release timing suggests the same: two tags six days apart, with the last push landing a minute and a half after the newer tag was published, which is the signature of a version bump rather than a batch of queued work.

## Conclusion

Install it from the published package rather than from the repository, because the stylesheet and the widgets are generated files and the repository carries a committed copy of both. Read the license from the manifest, not from the repository page, since the two name different permissive licenses. If you plan to modify it, build rather than edit: the scoped stylesheet is produced by a prefix-wrapping pass over one source file, so any change made to it directly is lost on the next build, and nothing in the manifest runs a test to catch it.

## FAQ

### How do I install ds.css?

Three ways are documented: a stylesheet link from unpkg pointing at the package name, copying the contents of the dist directory and linking that file, or installing from npm with `npm i @spiritov/ds.css`, where the package's main field points at the stylesheet inside dist.

### What does the scoped stylesheet in ds.css actually do?

You import ds-scoped.css instead of the main file and place the markup inside a parent element with class="ds-css". The scoped file is generated from the same source as the main one by a selector prefix-wrapping pass, so it styles only that container and a button outside it stays unstyled.

### Does ds.css ship web components?

Yes, as ES modules. The calendar is loaded with a module script and used as a ds-calendar element in the markup, and it can be pulled from unpkg, imported from a local copy under dist/widgets, or dynamically imported inside a framework lifecycle hook.

### What license is ds.css released under?

The two surfaces disagree. The repository's own metadata shows MIT while the package manifest declares ISC, and a LICENSE file sits at the repository root. Both are permissive, and anyone installing from npm sees the value declared in the manifest.

### How is ds.css built from source?

Three scripts run in order: two PostCSS passes over src/dev.css with different configurations produce dist/ds.css and dist/ds-scoped.css, then a shell copy moves assets, widgets and both stylesheets into dist. The manifest declares no test or lint script, so nothing verifies the generated output against the source.

## Sources

- [License: MIT](https://github.com/spiritov/ds.css/blob/main/LICENSE)
- [Project website](https://css.ds.dreamyard.xyz)
- [README](https://github.com/spiritov/ds.css/blob/main/README.md)
- [Releases](https://github.com/spiritov/ds.css/releases)
- [spiritov/ds.css on GitHub](https://github.com/spiritov/ds.css)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/spiritov-ds-css
