Skip to main content

solid-ui

solid-ui provides reusable UI components for building Solid applications — widgets, forms, tables, and styling utilities.

Installation

npm install solid-ui

Widgets

Buttons

import * as UI from 'solid-ui'

// Simple button
const btn = UI.widgets.button(
document,
'Click Me',
() => console.log('clicked')
)

// Button with icon
const iconBtn = UI.widgets.button(
document,
'+ Add',
handleAdd,
{ icon: 'plus' }
)

// Async button (shows loading state)
const saveBtn = UI.widgets.asyncButton(
document,
'Save',
async () => {
await saveData()
}
)

Text Fields

// Simple text input
const input = UI.widgets.textInputField(
document,
'Enter name...',
(value) => console.log('Changed:', value)
)

// With initial value
const nameField = UI.widgets.textInputField(
document,
'',
handleChange,
{ value: 'Alice' }
)

RDF-Bound Fields

import { store, ns } from 'solid-logic'

// Field bound to RDF property
const field = UI.widgets.field(
document,
store,
subject, // NamedNode - the resource
ns.foaf('name'), // NamedNode - the predicate
'Name' // Label
)

// Changes automatically saved to pod
// Create a clickable link to a resource
const link = UI.widgets.linkTo(
document,
sym('https://alice.example/profile/card#me'),
'View Alice\'s Profile'
)

// Link with custom styling
const styledLink = UI.widgets.linkTo(
document,
target,
label,
{ style: 'button' }
)

Avatars

// User avatar from WebID
const avatar = UI.widgets.avatar(
document,
sym('https://alice.example/profile/card#me'),
{ size: 48 }
)

// Avatar with fallback
const avatarWithFallback = UI.widgets.avatar(
document,
webId,
{ fallback: 'initials' }
)

Forms

Auto-Generated Forms

solid-ui can generate forms from RDF ontologies:

import * as UI from 'solid-ui'

// Generate form from a form specification
const form = UI.forms.buildForm(
document,
store,
subject,
formSpec, // NamedNode pointing to form definition
doc // Document to save to
)

container.appendChild(form)

Form Specification

Forms are defined in RDF using the UI ontology:

@prefix ui: <http://www.w3.org/ns/ui#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

<#PersonForm> a ui:Form ;
ui:parts (
<#nameField>
<#emailField>
) .

<#nameField> a ui:SingleLineTextField ;
ui:property foaf:name ;
ui:label "Name" ;
ui:required true .

<#emailField> a ui:EmailField ;
ui:property foaf:mbox ;
ui:label "Email" .

Field Types

// Available field types
UI.forms.fieldTypes = {
'SingleLineTextField': /* text input */,
'MultiLineTextField': /* textarea */,
'IntegerField': /* number input */,
'DecimalField': /* decimal number */,
'BooleanField': /* checkbox */,
'DateField': /* date picker */,
'DateTimeField': /* datetime picker */,
'EmailField': /* email input */,
'PhoneField': /* phone input */,
'ColorField': /* color picker */,
'Choice': /* dropdown/radio */,
'Classifier': /* type selector */,
}

Manual Form Building

// Build a form manually
const form = document.createElement('form')

// Name field
const nameGroup = UI.forms.fieldGroup(document, 'Name')
const nameInput = UI.forms.textField(document, store, subject, ns.foaf('name'))
nameGroup.appendChild(nameInput)
form.appendChild(nameGroup)

// Save button
const saveBtn = UI.widgets.asyncButton(document, 'Save', async () => {
await store.updater.update([], store.statementsMatching(null, null, null, subject.doc()))
})
form.appendChild(saveBtn)

Tables

Simple Table

// Create a table from RDF data
const subjects = store.each(container, ns.ldp('contains'), null, null)

const table = UI.widgets.table(
document,
subjects,
[
{ property: ns.rdfs('label'), label: 'Name' },
{ property: ns.dct('created'), label: 'Created' },
{ property: ns.dct('modified'), label: 'Modified' }
]
)

Editable Table

const editableTable = UI.widgets.editableTable(
document,
store,
subjects,
columns,
{
onAdd: async (newRow) => { /* handle add */ },
onDelete: async (row) => { /* handle delete */ },
sortable: true
}
)

Tabs

// Create a tabbed interface
const tabs = UI.widgets.tabs(document, [
{
label: 'Profile',
render: (container) => {
container.appendChild(profilePane)
}
},
{
label: 'Settings',
render: (container) => {
container.appendChild(settingsPane)
}
},
{
label: 'Activity',
render: (container) => {
container.appendChild(activityPane)
}
}
])

Styling

Apply Standard Styles

// Style an element
UI.style.styleElement(element, 'button')
UI.style.styleElement(element, 'input')
UI.style.styleElement(element, 'heading')
UI.style.styleElement(element, 'panel')

Style Classes

// Add SolidOS style classes
element.classList.add('solid-button')
element.classList.add('solid-input')
element.classList.add('solid-panel')
element.classList.add('solid-card')

Color Palette

// Access the color palette
UI.style.colors.primary // Main brand color
UI.style.colors.secondary // Secondary actions
UI.style.colors.success // Success states
UI.style.colors.warning // Warning states
UI.style.colors.error // Error states
UI.style.colors.muted // Disabled/muted

Dialogs

Alert Dialog

UI.widgets.alert('Operation completed successfully')

Confirm Dialog

const confirmed = await UI.widgets.confirm(
'Are you sure you want to delete this?'
)
if (confirmed) {
await deleteResource()
}

Prompt Dialog

const name = await UI.widgets.prompt('Enter your name:')
if (name) {
console.log(`Hello, ${name}`)
}

Custom Dialog

const dialog = UI.widgets.dialog(
document,
'Edit Profile',
(container) => {
// Build dialog content
const form = buildProfileForm()
container.appendChild(form)
},
{
buttons: [
{ label: 'Cancel', action: 'close' },
{ label: 'Save', action: handleSave, primary: true }
]
}
)

Icons

// Get an icon element
const icon = UI.icons.iconFor(subject) // Infers from type

// Specific icons
const folderIcon = UI.icons.icon('folder')
const personIcon = UI.icons.icon('person')
const fileIcon = UI.icons.icon('file')
const lockIcon = UI.icons.icon('lock')

Loading States

// Show loading indicator
const loader = UI.widgets.loadingIndicator(document)
container.appendChild(loader)

// Later, remove it
loader.remove()

// Or use with async operations
const content = await UI.widgets.withLoading(
container,
async () => {
return await fetchData()
}
)

Error Handling

// Display an error message
UI.widgets.errorMessage(
container,
'Failed to load data',
error // optional Error object
)

// With retry button
UI.widgets.errorMessage(
container,
'Connection failed',
error,
{ retry: () => fetchData() }
)

Integration Example

Here's a complete example using solid-ui in a pane:

import * as UI from 'solid-ui'
import { store, authn, ns } from 'solid-logic'

const personPane = {
name: 'person',
icon: UI.icons.icon('person'),

label: (subject) => {
if (store.holds(subject, ns.rdf('type'), ns.foaf('Person'))) {
return 'Person'
}
return null
},

render: (subject, dom, context) => {
const container = dom.createElement('div')
UI.style.styleElement(container, 'panel')

// Header with avatar
const header = dom.createElement('div')
header.style.display = 'flex'
header.style.alignItems = 'center'
header.style.gap = '12px'

const avatar = UI.widgets.avatar(dom, subject, { size: 64 })
header.appendChild(avatar)

const name = store.any(subject, ns.foaf('name'), null, null)
const heading = dom.createElement('h2')
heading.textContent = name?.value || 'Unknown'
header.appendChild(heading)

container.appendChild(header)

// Editable fields (if owner)
const user = authn.currentUser()
if (user && user.uri === subject.uri) {
const nameField = UI.widgets.field(
dom, store, subject, ns.foaf('name'), 'Name'
)
container.appendChild(nameField)

const bioField = UI.widgets.field(
dom, store, subject, ns.foaf('bio'), 'Bio'
)
container.appendChild(bioField)
}

// Friends list
const friends = store.each(subject, ns.foaf('knows'), null, null)
if (friends.length > 0) {
const heading = dom.createElement('h3')
heading.textContent = 'Friends'
container.appendChild(heading)

const list = dom.createElement('ul')
friends.forEach(friend => {
const li = dom.createElement('li')
li.appendChild(UI.widgets.linkTo(dom, friend))
list.appendChild(li)
})
container.appendChild(list)
}

return container
}
}

See Also