Components
@blobs/sdk-solid is the SolidJS component library for Blobs. Most of it is ordinary UI — buttons, inputs, list items. A small set of components are elevated: each one is a complete feature with a public contract that other apps build on. This section documents those components.
What is an elevated component?
A component is elevated when it meets all of these conditions:
- It owns a complete feature. For example, the Blob Editor is the whole authoring surface for a blob body, not one field.
- It has a stable, documented contract. Its props, callbacks and extension types are public API. They are versioned with
@blobs/sdk-solid, and every member has TSDoc that this site renders. - It is extendable. Apps add behaviour through plugins or registries. They do not fork the component.
- It is themed only through CSS variables. It uses the same design tokens as the rest of the app, so every theme restyles it.
| Category | Component | What it is |
|---|---|---|
| Blobs | Blob Editor | The editor for a blob body, with the plugin (extension) API and the embed system. |
| Blobs | Blob List Item | One blob as a list row — title, badges, preview (markdown rendered), thumbnails, metadata and actions. |
More components will be added to this section over time.
Consuming components
Install the SDK and the component library. Then import the bundled stylesheet once, at the entry point of your app:
npm install @blobs/sdk @blobs/sdk-solidimport '@blobs/sdk-solid/styles.css';import { render } from 'solid-js/web';import App from './App';
render(() => <App />, document.getElementById('root')!);Import components and their types by name from the package root:
import { BlobContentEditor, type BlobContentEditorProps } from '@blobs/sdk-solid';Most elevated components need an SDK instance for API calls (createSDK() from @blobs/sdk). Give it as a prop or through a host object, as each page shows. Do not create the SDK inside a component.
Theming
Components do not contain hard-coded colours, fonts or radii. They read CSS custom properties, for example:
| Variable | Used for |
|---|---|
--color-primary | Accent, active states, links |
--color-text-primary / --color-text-secondary | Body and muted text |
--color-border | Borders and dividers |
--surface-card / --surface-popover | Panels, and dropdowns or popovers that float above content |
--radius-sm … --radius-lg | Corner radii |
--shadow-dropdown | Floating UI shadow |
--font-size-*, --font-mono | Type scale and code font |
--animation-speed | Transition durations |
A theme is a ThemeConfig object. themeLoader.loadTheme(theme) flattens it and writes each mapped value onto <html> as a CSS variable. The library includes five themes: defaultTheme, neoTheme, deepSpaceTheme, velvetPageTheme and paperLightTheme.
import { themeLoader, deepSpaceTheme } from '@blobs/sdk-solid';
themeLoader.loadTheme(deepSpaceTheme);To restyle a component without a full theme, set the variables yourself on any ancestor:
.my-editor-host { --color-primary: #e11d48; --surface-popover: #111827;}Extensions and plugins
Each elevated component has its own extension points, but they all follow the same rules:
- Opt-in and composable. Extensions are plain objects or factories (
createXPlugin(options)) that you give in a prop such asplugins. With no extensions, the component still works. - Fault-isolated. The component catches errors that a hook throws and writes them to the log. An extension that fails cannot crash the component that hosts it.
- Host services, not globals. The component gives an extension a host object (SDK, current blob, files, navigation, notifications). Every member is optional, so an extension must continue to work when a service is not there (for example, a draft blob has no id yet).
- Registries for rendering. Content that must render the same in the editor and in read-only viewers (for example, link previews or mentions) uses a module-level registry, such as
registerEmbedParser. Register it once, at app start or when a plugin module loads, and every surface uses it. - Themed with the same variables. Extension UI uses the CSS variables above, so it follows the theme automatically.
The Blob Editor extensions section has the full plugin API.
Documenting a component
The prop and option tables on these pages are generated from the TSDoc in packages/sdk-solid/src. Do not write them by hand.
The pipeline:
packages/sdk-solid/src/**/*.ts(x) TSDoc on exported interfaces / type aliases │ ▼ apps/docs/tools/generate-component-docs.mjs (TypeScript compiler API) │ driven by apps/docs/tools/components.manifest.json ▼apps/docs/src/components/generated/props/<Name>.json │ ▼ <PropsTable of="Name" /> (apps/docs/src/components/PropsTable.astro)MDX page under src/content/docs/v1/components/To add a component (or a new options interface):
-
Write TSDoc on the source. Each exported interface or type alias needs a summary. Each member needs a description. Add
@defaultwhen a default applies, and@deprecatedwhen applicable. You can use light markdown:`code`,**bold**,[links](…), blank-line paragraphs and-lists.export interface ToolbarPluginOptions {/*** Where the toolbar renders.* @default 'static'*/mode?: 'static' | 'floating' | 'contextmenu';} -
Add the declaration to the manifest (
apps/docs/tools/components.manifest.json), under the component it documents. Set"optional": truefor an API that does not exist yet. The table then shows a “not available yet” note and the build does not fail. Set"file"only when two files export the same name. -
Regenerate:
Terminal window cd apps/docsnpm run docs:components # write src/components/generated/props/*.jsonnpm run docs:components:check # exit 1 if the committed JSON is stalenpm run buildruns the generator automatically (it is theprebuildscript). Themake deploy-docstarget runsastro builddirectly on the server and does not regenerate. So commit the generated JSON together with the source change. -
Write or update the page. Put the page at
src/content/docs/v1/components/<category>/<component>.mdx, add it to the Components group inastro.config.mjs, and render tables with:import PropsTable from '../../../../../components/PropsTable.astro';<PropsTable of="BlobContentEditorProps" />PropsTableacceptsdescription={false}to hide the TSDoc summary of the declaration, andsource={false}to hide the GitHub source link.
What the generator records for each member: name, type (as written in the source), required or optional, readonly, default (@default / @defaultValue), description, deprecation, and the interface that the member is inherited from. Literal-union type aliases (for example, EditorPluginTrigger) render as a list of values.