Submit in two minutes (no tools needed)
Tip: build it in the Playground first. Hit Save & share, then Submit as component, and the form arrives prefilled with your playground link.
It's a GitHub issue form. Either:
- Paste your component: its code, a preview snippet and some docs, or
- Link your repository: paste the URL of the public GitHub repo (or folder) your component lives in. We take the file that calls
rocket('sb-…')and theREADME.mdnext to it, and pin the exact commit. Editing the issue later re-syncs from the repo.
A bot validates the component, loads it in a headless browser to generate its API reference, and opens a pull request with you as the author. If something is wrong, it comments on the issue: fix the issue and it tries again. You can update your own component later the same way.
Or open a pull request
Prefer git? Fork the repository and:
go tool task new -- my-widget --category formsscaffoldscomponents/my-widget/.- Build it in
my-widget.jsand document it inREADME.md.go tool task liveshows it at/components/my-widget. go tool task manifestsgeneratesmanifest.json(it needs Chrome or Chromium), andgo tool task testvalidates everything.- Open the pull request. CI runs the same checks.
There is no Go code to touch: the gallery, search, categories, docs page and Playground all come from your folder.
Anatomy of a component
components/my-widget/
├── README.md front matter + docs; html preview blocks become live demos
├── my-widget.js rocket('sb-my-widget', { ... })
└── manifest.json generated in dev from Rocket's manifest, commit it
The front matter drives the gallery card:
---
name: My Widget
tag: sb-my-widget # must start with sb-
category: forms # forms · navigation · feedback · layout · media · data · utilities · experimental
summary: One line, at most 90 characters.
author: your-github-handle
tags: [keywords, for, search]
since: 2026-09-21
preview: |
<sb-my-widget></sb-my-widget>
---
A fenced block tagged html preview renders twice: live on the page and as copyable source.
```html preview
<sb-my-widget size="lg"></sb-my-widget>
```
Your page automatically gets a Playground built from the manifest. Add a playground: block to the front matter to set slider ranges (props), starting values, slotted content, a style or static attrs for the live element (useful for arrays and JSON props, which get no control), or to exclude props.
House rules
- Shadow DOM and tokens. Use Rocket's default
openmode. Style only through--sb-*semantic tokens, each with a fallback:--_bg: var(--sb-surface-card, #141D32). That makes the Themes page work, and the component still works outside Starbase. - State lives in
$$signals, not attributes. Datastar apps morph server HTML into the page. If your component writes interaction state back to its own attributes, the next morph resets it. Initialise$$from props, update$$, and expose live values withoverridePropgetters. - Speak Datastar. Emit bubbling, composed events with
emit(), sodata-on:*works on any ancestor. Expose avalueorcheckedproperty and firechangeorinput, sodata-bindworks. In your docs, declare bound signals withdata-signalsfirst, and bind components with__prop:data-bind:_x__prop.value. Datastar binds before custom elements are upgraded, and without__propit falls back to thevalueattribute, which the next server morph removes. Host getters and setters that read or write$$must wrap the access instartPeeking()/stopPeeking(). Otherwisedata-bind's own effect subscribes to your internal signal and writes stale values back. Also adopt avaluethatdata-bindset before your element was upgraded (seeearly()in the slider). - Signal-driven attributes survive morphs only when preserved. When a page drives a component with
data-attr:yaw="$_yaw", adddata-preserve-attr="yaw"to the host. The server morph copies attributes from its own markup, and without this it would reset them on every frame. - Wire it declaratively. In
render, usedata-on:*with local actions (action('press', …)→data-on:click="@press()"), anddata-bind,data-show,data-class,data-textand<template data-for>on$$signals. Get elements throughdata-refandonFirstRender({ refs }). Reach foraddEventListeneronly where no attribute exists (matchMedia, observers). - Accessible by default. Use native elements inside the shadow root, keyboard support, visible focus, and
prefers-reduced-motion. - Document the API. Give every prop
.docs({ description })and declare slots and events inmanifest. The API tables are generated from it. - Few dependencies. Import from
'datastar', or relatively from files inside your own folder (for example a vendored ES module invendor/, together with its licence). Keep it small.
Review
A maintainer checks that the component renders in the gallery and on the Themes page, that go test ./... passes, and that the docs examples work. Components are published under the MIT licence with you as the author.