Skip to content

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.
CategoryComponentWhat it is
BlobsBlob EditorThe editor for a blob body, with the plugin (extension) API and the embed system.
BlobsBlob List ItemOne 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:

Terminal window
npm install @blobs/sdk @blobs/sdk-solid
index.tsx
import '@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:

VariableUsed for
--color-primaryAccent, active states, links
--color-text-primary / --color-text-secondaryBody and muted text
--color-borderBorders and dividers
--surface-card / --surface-popoverPanels, and dropdowns or popovers that float above content
--radius-sm … --radius-lgCorner radii
--shadow-dropdownFloating UI shadow
--font-size-*, --font-monoType scale and code font
--animation-speedTransition 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:

  1. Opt-in and composable. Extensions are plain objects or factories (createXPlugin(options)) that you give in a prop such as plugins. With no extensions, the component still works.
  2. 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.
  3. 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).
  4. 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.
  5. 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):

  1. Write TSDoc on the source. Each exported interface or type alias needs a summary. Each member needs a description. Add @default when a default applies, and @deprecated when 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';
    }
  2. Add the declaration to the manifest (apps/docs/tools/components.manifest.json), under the component it documents. Set "optional": true for 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.

  3. Regenerate:

    Terminal window
    cd apps/docs
    npm run docs:components # write src/components/generated/props/*.json
    npm run docs:components:check # exit 1 if the committed JSON is stale

    npm run build runs the generator automatically (it is the prebuild script). The make deploy-docs target runs astro build directly on the server and does not regenerate. So commit the generated JSON together with the source change.

  4. Write or update the page. Put the page at src/content/docs/v1/components/<category>/<component>.mdx, add it to the Components group in astro.config.mjs, and render tables with:

    import PropsTable from '../../../../../components/PropsTable.astro';
    <PropsTable of="BlobContentEditorProps" />

    PropsTable accepts description={false} to hide the TSDoc summary of the declaration, and source={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.