solid-ui

User Interface widgets and utilities for Solid (solid-ui)
These are HTML5 widgets which connect to a solid store. Building blocks for solid-based apps.
Vanilla JS. Includes large widgets like chat, table, matrix, form fields, and small widgets.
See Solid-Ui Storybook for UI widgets.
See Solid-UI API for UI functions.
See Forms introduction for UI vocabulary implementation.
Table of Contents
Getting started
Contributions of bug fixes and new functionality, documentation, and tests are
always appreciated.
Install via npm
npm install solid-ui rdflib solid-logic
Then import in your JavaScript/TypeScript code:
import * as UI from 'solid-ui'
import * as $rdf from 'rdflib'
import * as SolidLogic from 'solid-logic'
const button = UI.widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
'Click me',
() => alert('Button clicked!')
)
document.body.appendChild(button)
Use Directly in a Browser
Solid-UI provides both UMD and ESM bundles for direct browser usage. Both bundles externalize rdflib and solid-logic, which must be loaded separately.
Available Files
- UMD (Universal Module Definition):
- Development:
dist/solid-ui.js (exposes global window.UI)
- Production:
dist/solid-ui.min.js (minified)
- ESM (ES Modules):
- Development:
dist/solid-ui.esm.js
- Production:
dist/solid-ui.esm.min.js (minified)
UMD Bundle (Global Variable)
If you use the legacy UMD bundle (solid-ui.js / solid-ui.min.js), rdflib must define window.$rdf before solid-ui loads. If rdflib is missing, solid-ui will throw ReferenceError: $rdf is not defined.
Load via <script> tags and access through global variables window.$rdf, window.SolidLogic, and window.UI.
<!DOCTYPE html>
<html>
<head>
<title>Solid-UI UMD Example</title>
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/rdflib/dist/rdflib.min.js"></script>
<script src="https://unpkg.com/solid-logic/dist/solid-logic.min.js"></script>
<script src="https://unpkg.com/solid-ui/dist/solid-ui.min.js"></script>
<script>
const { store, authn } = window.SolidLogic
const { widgets } = window.UI
const webId = authn.currentUser()
if (webId) {
const userButton = widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
`Logged in as: ${webId.value}`,
() => alert(`Your WebID: ${webId.value}`)
)
document.getElementById('app').appendChild(userButton)
} else {
const loginButton = widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
'Login to Solid',
() => authn.checkUser().then(() => location.reload())
)
document.getElementById('app').appendChild(loginButton)
}
</script>
</body>
</html>
ESM Bundle (Import as Module)
Use modern JavaScript modules with import statements.
<!DOCTYPE html>
<html>
<head>
<title>Solid-UI ESM Example</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import * as $rdf from 'https://esm.sh/rdflib'
import * as SolidLogic from 'https://esm.sh/solid-logic@4.0.1'
import * as UI from 'https://esm.sh/solid-ui@3.0.1'
const webId = SolidLogic.authn.currentUser()
if (webId) {
const userName = await getUserName(webId)
const userButton = UI.widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
userName || 'My Profile',
() => window.open(webId.value, '_blank')
)
document.getElementById('app').appendChild(userButton)
} else {
const loginButton = UI.widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
'Login to Solid',
async () => {
await SolidLogic.authn.checkUser()
location.reload()
}
)
document.getElementById('app').appendChild(loginButton)
}
async function getUserName(webId) {
try {
await SolidLogic.store.fetcher.load(webId.doc())
const name = SolidLogic.store.any(webId, $rdf.sym('http://xmlns.com/foaf/0.1/name'))
return name ? name.value : null
} catch (error) {
console.error('Error fetching user name:', error)
return null
}
}
</script>
</body>
</html>
ESM Bundle with Import Maps
Use import maps for cleaner module specifiers:
<!DOCTYPE html>
<html>
<head>
<title>Solid-UI ESM with Import Maps</title>
</head>
<body>
<div id="app"></div>
<script type="importmap">
{
"imports": {
"rdflib": "https://esm.sh/rdflib",
"solid-logic": "https://esm.sh/solid-logic@4.0.1",
"solid-ui": "https://esm.sh/solid-ui@3.0.1"
}
}
</script>
<script type="module">
import * as $rdf from 'rdflib'
import * as SolidLogic from 'solid-logic'
import * as UI from 'solid-ui'
const app = document.getElementById('app')
async function createUserButton() {
const webId = SolidLogic.authn.currentUser()
if (!webId) {
const loginBtn = UI.widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
'Login',
() => SolidLogic.authn.checkUser()
)
app.appendChild(loginBtn)
return
}
try {
await SolidLogic.store.fetcher.load(webId.doc())
const name = SolidLogic.store.any(
webId,
$rdf.sym('http://xmlns.com/foaf/0.1/name')
)
const profileBtn = UI.widgets.button(
document,
'https://solidproject.org/assets/img/solid-emblem.svg',
name ? name.value : 'My Profile',
() => {
alert(`WebID: ${webId.value}\nName: ${name ? name.value : 'Not set'}`)
}
)
app.appendChild(profileBtn)
} catch (error) {
console.error('Error loading profile:', error)
}
}
createUserButton()
</script>
</body>
</html>
Web Components
solid-ui ships self-contained Lit-based custom elements as subpath exports. Each component is independently importable, registers its custom element on import, and ships its own styles encapsulated in a Shadow DOM.
Component UMD bundles do not export a shared global like window.UI. They only register the custom element on import, while the legacy main bundle still provides the UI global.
A header bar with branding, auth state (logged-out / logged-in), an account dropdown, an optional logout icon, and a desktop-only help menu.
Subpath export: solid-ui/components/header
import { Header } from 'solid-ui/components/header'
import type { HeaderMenuItem, HeaderAccountMenuItem, HeaderAuthState } from 'solid-ui/components/header'
<solid-ui-header theme="dark" layout="desktop" brand-link="/">
<a slot="help-menu" href="/help">Help</a>
</solid-ui-header>
Importing this module automatically registers <solid-ui-header> as a custom element.
solid-ui-login-button
A standalone login button that encapsulates the Solid OIDC login flow and emits login-success when authentication succeeds.
Subpath export: solid-ui/components/login-button
import { LoginButton } from 'solid-ui/components/login-button'
<solid-ui-login-button label="Log in" issuer-url="https://solidcommunity.net" icon="https://example.com/login-icon.svg"></solid-ui-login-button>
const loginButton = document.querySelector('solid-ui-login-button') as LoginButton
loginButton.addEventListener('login-success', (event: CustomEvent) => {
console.log('Logged in as', event.detail.webId)
})
solid-ui-signup-button
A standalone sign-up button that opens a signup URL in a new browser tab.
Subpath export: solid-ui/components/signup-button
import { SignupButton } from 'solid-ui/components/signup-button'
<solid-ui-signup-button label="Get a Pod" signup-url="https://solidproject.org/get_a_pod" icon="https://example.com/icon.svg"></solid-ui-signup-button>
Component build pipeline
Web components use a two-stage build to produce a clean public runtime layout while keeping internal TypeScript artifacts separate:
- webpack (
npm run build-dist) bundles each component entrypoint and emits the runtime files to dist/components/<name>/index.js and dist/components/<name>/index.esm.js.
- tsc (
npm run build-js) emits internal declaration and JS artifacts mirroring the source tree under dist/v2/components/<name>/.
scripts/build-component-dts.mjs (runs automatically after tsc as part of postbuild-js) writes thin public declaration wrappers at dist/components/<name>/index.d.ts, re-exporting from the internal dist/v2/components/<name>/ output.
This keeps the package.json subpath export fully aligned while exposing only the public dist/components/... layout:
"./components/header": {
"types": "./dist/components/header/index.d.ts",
"import": "./dist/components/header/index.esm.js",
"require": "./dist/components/header/index.js"
}
Consumers never import from dist/v2/components/...; that path is an internal build artifact only.
Development
When developing a component in solid-ui you can test it in isolation using storybook
npm run build
npm run storybook
If there is no story for the component yet, add a new one to ./src/stories.
When you want to test the component within a solid-pane, you can use the development mode of solid-panes.
Adding Tests
One can run extisting tests with:
npm run test
or with coverage
npm run test-coverage
The following document gives guidance on how to add and perform testing in solid-ui.
Testing in solid-ui
GitHub Pages
- The github pages should contain the storybook and further documentation. In order to make sure it is deployed there is a step in the CI (gh-pages). This depends on the previous
build step. It MUST contain build-storybook otherwise the storybook is not being published.
Further documentation
Generative AI usage
The SolidOS team is using GitHub Copilot integrated in Visual Studio Code.
We have added comments in the code to make it explicit which parts are 100% written by AI.
Prompt usage history:
-
Raptor mini: If I want to make the header a web component with a self contained CSS which only consumes CSS variables from a theme, how would I do this?
-
Raptor mini: Go ahead and create a header web component, for backward compatibility keep the current code too.
In the new header component I need to be flexible and receive from consumer - the layout (mobile or desktop) and the theme (light or dark) and its according CSS variables for light to dark.
-
Raptor mini: Propose code. how about webpack config for distribution?
-
Raptor mini: pls add a readme in the component documenting it usage and test and all
-
Raptor mini: the helpMenuList should be menu items inside the help icon drop down menu
-
Raptor mini: When I am not logged in I want the header to display: Log in button and Sign Up button.
When the user is logged in, there is only one button, a drop down button called Accounts. The icon of the button is the avatar of the profile and it displays a list of available accounts of the user.
I want this all to be presented flexible in the component.
-
Claude Sonnet 4.6: create a LitElement also for the signupButton in the SignupButton.ts based on the signup.js code and wire it into the header like you did the loginButton.
-
Raptor mini: when we are on layout mobile we do not want to display the help menu at all.
-
Raptor mini: Create for me a footer Lit Component in tsy style of the components I have and under v2. Take the code from this index.ts to start with.
-
Raptor mini: Good. Now, I want the footer to be a rectangular with round corners, grey background and it should have an adjustable position.
-
Raptor mini: The content of the footer should be different upon loggedin or not.
If not logged in, it should say:
Title Public View
You are viewving this profile as a guest,
And if logged in:
Title: Logged in View
You are logged in as nameOfLoggedIn user.
-
Raptor mini: add a readme to the Footer component with example.
-
Claude Sonnet 4.6: Make the dop down as a list under the input field and entlarge the pop up, make it higher, adjustable to fit the drop down. And make the drop down arrow area larger