@ulb-darmstadt/shacl-form
v3.4.2
Published
SHACL form generator
Maintainers
Readme
SHACL Form Generator
An HTML5 web component to edit, view and query RDF data that conform to SHACL shapes.
See demo here
Basic usage
<html>
<head>
<!-- load the bundled web component (for app development, use: npm i @ulb-darmstadt/shacl-form) -->
<script src="https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form/dist/bundle.js" type="module"></script>
</head>
<body>
<!--
Provide SHACL shapes via the data-shapes attribute
or load them from a URL with data-shapes-url.
-->
<shacl-form data-shapes="
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix ex: <http://example.org#> .
ex:ExampleShape
a sh:NodeShape, rdfs:Class ;
sh:property [
sh:name 'my value' ;
sh:path ex:exampleValue ;
sh:maxCount 3 ;
] .
"></shacl-form>
<script>
const form = document.querySelector("shacl-form")
form.addEventListener('change', event => {
// check if form data validates according to the SHACL shapes
if (event.detail?.valid) {
// serialize the RDF graph and log it
const triples = form.serialize()
console.log('entered form data', triples)
// store the data somewhere, e.g. in a triple store
}
})
</script>
</body>
</html>Install and use in your project
Install the package:
npm i @ulb-darmstadt/shacl-formThis package has peer dependencies; install them in your app as well:
npm i @ro-kit/ui-widgets jsonld leaflet leaflet-editable leaflet.fullscreen leaflet.heat n3 rdfxml-streaming-parser shacl-engine uuidLoad the web component in your app. For a Vite/webpack-style project, import it once at startup:
import '@ulb-darmstadt/shacl-form'Then use the element in your HTML:
<shacl-form data-shapes="..."></shacl-form>Alternatively, load the prebuilt bundle directly in a plain HTML page, as shown above:
<script src="https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form/dist/bundle.js" type="module"></script>Element attributes
Attribute | Description
---|---
data-shapes | SHACL shape definitions (e.g. Turtle) used to generate the form
data-shapes-url | When data-shapes is not set, load SHACL shapes from this URL
data-shape-subject | Optional subject IRI for the root sh:NodeShape. If not set, the <shacl-form> first tries to resolve the root shape from data-values-subject and loaded data, preferring dcterms:conformsTo, then rdf:type / sh:targetClass, and finally falling back to the first node shape in the shapes graph
data-values | RDF triples (e.g. Turtle) used to prefill the form
data-values-url | When data-values is not set, load RDF triples from this URL
data-values-subject | Subject (IRI or blank node id) for generated data. If not set, a blank node with a new UUID is created. If data-values or data-values-url is set, this id is used to find the root node in the data graph. When exactly one dcterms:conformsTo statement exists in the loaded data graph, its subject is used automatically. For the selected subject, a dcterms:conformsTo object that is a known sh:NodeShape is used as the root shape before falling back to rdf:type / sh:targetClass
data-values-namespace | RDF namespace used when generating new RDF subjects. Default is empty, which yields blank nodes
data-values-graph | If set, serialization creates a named graph with this IRI
data-preserve-unmapped-values | If set, toRDF() overlays the current form values onto the complete original values dataset instead of returning only SHACL-managed values. Removed form values are replaced, unrelated triples and named graphs are retained, and blank-node subgraphs orphaned by an edit are removed
data-language | Language for langString values (e.g. in sh:name or rdfs:label). Default is navigator.language with fallback to navigator.languages
data-loading | Text displayed while the component initializes. Default is "Loading..."
data-ignore-owl-imports | By default, owl:imports URLs are fetched and merged into the shapes graph. Set this attribute to disable that behavior
data-view | When set, turns the component into a viewer that displays the data graph without editing
data-mode | Explicit component mode: edit (default), view, or query. When omitted, the legacy data-view attribute continues to select viewer mode
data-collapse | When set, sh:groups and properties with sh:node and sh:maxCount != 1 are rendered in a collapsible accordion. Use value "open" to start expanded
data-submit-button | [Ignored when data-view is set] Adds a submit button. The attribute value is used as the label. submit events fire only when the data validates
data-generate-node-shape-reference | When generating RDF data, adds a triple that references the root sh:NodeShape. Default predicate is http://purl.org/dc/terms/conformsTo. Set to an empty string to disable
data-show-node-ids | Show node shape subject ids in the form
data-show-root-shape-label | If set and the root shape has rdfs:label or dcterms:title, display that value as a heading
data-proxy | Proxy URL used by the built-in fetch-based URL loader. The requested RDF URL is appended to the proxy value, e.g. http://your-proxy.org/?url=. This is ignored when setRdfUrlResolver(...) is used
data-dense | Boolean to render a compact form with smaller paddings and margins. Default is true
data-hierarchy-colors | Comma-separated list of CSS colors for nested hierarchy bars. If unset, a default palette is used
data-use-shadow-root | Boolean string indicating whether <shacl-form> renders into a shadow root. Default is "true". Set to "false" to render into light DOM
Element methods
toRDF(graph?: Store): StoreWrites the current form values into the given graph. If no graph is provided, a new N3 Store is created.
When data-preserve-unmapped-values is set, the original values dataset is added
to the destination graph first. Original values managed by rendered SHACL fields
are replaced with their current form values, while unmapped triples are retained.
The default remains projection-only serialization.
serialize(format?: string, graph?: Store): stringSerializes an RDF graph in one of the supported output formats. If no graph is provided, serialize() first calls toRDF(). The default format is text/turtle.
validate(ignoreEmptyValues?: boolean): Promise<ValidationReport>Validates the current form data against the SHACL shapes graph and returns a validation report. Validation messages are also rendered next to the affected fields. If ignoreEmptyValues is true, empty required fields are not marked invalid. This method also runs automatically on change and submit.
registerPlugin(plugin: Plugin)Registers a plugin that customizes how certain values are edited or displayed. Plugins can target specific RDF predicates, xsd:datatypes, or both. Example: Leaflet
setTheme(theme: Theme)Sets the theme used to render the form. See Theming.
setClassInstanceProvider((className: string) => Promise<string>)Sets a callback used to provide RDF instances for sh:class values. See below for details.
setRdfUrlResolver((url: string) => Promise<string>)Sets a callback used whenever shacl-form loads RDF from a URL. See below for details.
setResourceLinkProvider(provider: ResourceLinkProvider)Registers a provider for linking existing resources. It can list resources that conform to a node shape and load RDF for the selected resources. See below.
setQueryFacetProvider(provider: QueryFacetProvider)See the query mode guide
getQuery(): QuerySee the query mode guide
refreshQueryFacets()See the query mode guide
Features
Validation
In edit mode, <shacl-form> validates the constructed data graph using shacl-engine and displays validation results as icons next to the relevant fields.
Data graph binding
<shacl-form> requires only a shapes graph as input via data-shapes (or data-shapes-url) to generate an empty form and create new RDF data from user input. Using data-values (or data-values-url) and data-values-subject, you can also bind an existing data graph to the form and prefill the fields.
Viewer mode
<shacl-form> is both an editor and a viewer. Set data-view and bind a shapes graph and a data graph to render a read-only view. See the demo.
Query mode
See the query mode guide and the demo.
Additional RDF for the shapes graph
Besides data-shapes and data-shapes-url, shacl-form can enrich the shapes graph in three ways:
owl:importsWhile parsing the shapes graph, any
owl:importspredicate with a valid HTTP URL is fetched, parsed, and added as a named graph. That graph is scoped to the node where theowl:importsstatement appears and its child nodes.The example shapes graph contains the following triples:
example:Attribution sh:property [ owl:imports <https://w3id.org/nfdi4ing/metadata4ing/> ; sh:name "Role" ; sh:path dcat:hadRole ; sh:class prov:Role ; ] .Here the imported ontology defines instances of
prov:Role, which populate the "Role" dropdown. The imported graph is only available while rendering and validating this specific property.setRdfUrlResolver((url: string) => Promise<string>)Use this when RDF URLs need authentication, proxying, custom routing, or application-level caching. The resolver is used for:
data-shapes-urldata-values-url- fallback shape resolution from data values
- recursive
owl:imports
The resolver must return RDF as a string in any of the supported formats, for example
text/turtle. If no resolver is configured,shacl-formuses the default fetch-based behavior. In that default mode,data-proxycan be used to route requests through a proxy without custom JavaScript.Caching behavior: With the default fetch path, URLs are cached across form instances by URL. When
setRdfUrlResolver(...)is used, that built-in cache is bypassed and caching becomes the responsibility of the resolver implementation. Duplicateowl:importsURLs are still de-duplicated within a single form load.Example:
form.setRdfUrlResolver(async (url) => { const response = await fetch(`/api/rdf?url=${encodeURIComponent(url)}`) return response.text() })Resolver-returned RDF is parsed through the same pipeline as the default fetch path.
setClassInstanceProvider((className: string) => Promise<string>)Use this when a property has
sh:classand the available instances should come from an external source. The callback returns RDF, for exampletext/turtle, containing instances of the requested class.In this example, the code:
form.setClassInstanceProvider((clazz) => { if (clazz === 'http://example.org/Material') { return ` <http://example.org/steel> a <http://example.org/Material>; <http://www.w3.org/2000/01/rdf-schema#label> "Steel". <http://example.org/wood> a <http://example.org/Material>; <http://www.w3.org/2000/01/rdf-schema#label> "Wood". <http://example.org/alloy> a <http://example.org/Material>; <http://www.w3.org/2000/01/rdf-schema#label> "Alloy". <http://example.org/plaster> a <http://example.org/Material>; <http://www.w3.org/2000/01/rdf-schema#label> "Plaster". ` } })returns instances of
http://example.org/Material, which populate the "Artwork material" dropdown. In a real application, the callback would often call an API or triple store.
Use of SHACL sh:class
When a property shape has an sh:class, shacl-form scans the shapes, data, and current-values graphs plus any owl:imports graphs visible in that shape subtree. Imports declared on sibling subtrees remain isolated. rdfs:subClassOf is also considered when building the list of class instances.
shacl-form also supports class instance hierarchies modelled with skos:broader and/or skos:narrower. This is illustrated by the "Subject classification" property in the example.
SHACL constraints sh:or and sh:xone
<shacl-form> supports sh:or and sh:xone to let users choose between different options on nodes or properties. The example shapes graph includes:
example:Attribution
a sh:NodeShape ;
sh:property [
sh:maxCount 1 ;
sh:minCount 1 ;
sh:path prov:agent ;
sh:or (
[ sh:node example:Person ; rdfs:label "Person" ]
[ sh:node example:Organisation ; rdfs:label "Organisation" ]
)
] .When adding a new attribution, <shacl-form> renders a dropdown to select Person/Organisation. After selection, the dropdown is replaced by the input fields of the chosen node shape.
When binding an existing data graph to the form, the constraint is resolved based on the data value:
- For RDF literals, an
sh:oroption with a matchingsh:datatypeis chosen - For blank nodes or named nodes, the
rdf:typeis matched with a node shape having a correspondingsh:targetClassor with a property shape having a correspondingsh:class. If there is nordf:typebut ash:nodeKindofsh:IRI, the node id is used as the value
SHACL alternative paths
<shacl-form> supports sh:alternativePath lists whose members are predicate IRIs:
sh:property [
sh:path [ sh:alternativePath ( schema:name foaf:name ) ] ;
sh:name "Name"
] .Values using any listed predicate are displayed in the same property and retain their original predicates when serialized. When adding a value, the form first asks which predicate to use. Predicate-specific plugins are resolved against that selected or loaded predicate. Query mode represents the alternatives as a single union path without showing the predicate chooser.
An alternative property may be accompanied by one direct-path sibling property
for each listed predicate. When those siblings do not define independent
cardinality constraints, they are treated as branch descriptors: only the
alternative chooser is initially rendered, and the selected sibling supplies
editor constraints such as sh:nodeKind, sh:datatype, and its label. Siblings
with their own cardinality remain independent properties.
Nested sequence, inverse, and repetition paths inside sh:alternativePath are
not currently supported.
Linking existing data
When a node shape has a sh:targetClass and any available graph contains instances of that class, those instances can be linked in the corresponding SHACL property. The generated data graph contains only a reference to the linked instance, not its full triples.
Graphs considered are:
- the shapes graph
- the data graph
- any graph loaded via
owl:imports - triples provided by classInstanceProvider
If your graphs only contain resource identifiers (IRIs) and not the full triples for linked resources, use setResourceLinkProvider to supply them on demand.
ResourceLinkProvider can:
- list resources that conform to a node shape so they can appear in the "Link existing ..." dialog
- load RDF for selected resource IRIs so
shacl-formcan resolve, display, and validate linked resources
The RDF returned by loadResources(...) can use any of the supported formats. It is parsed through the same RDF-loading pipeline used for owl:imports, inline data, and classInstanceProvider.
The provider supports both eager loading during initialization and lazy loading when the user opens the link dialog. See here for an example implementation.
For properties with sh:or or sh:xone node-shape alternatives, first choose the type of node to use. The selected branch then provides separate controls for creating a new node or linking a compatible node. Link candidates combine nodes already rendered elsewhere in the form with resources supplied by ResourceLinkProvider.
SHACL shape inheritance
SHACL defines two ways of inheriting shapes: sh:and and sh:node. <shacl-form> supports both. In this example, node shape example:ArchitectureModelDataset extends example:Dataset by defining:
example:ArchitectureModelDataset sh:node example:Dataset .Properties of inherited shapes are displayed first.
Plugins
Plugins can modify rendering and add edit/view functionality for specific RDF datatypes or predicates (or both). For example, the JavaScript on this page includes:
import { LeafletPlugin } from '@ulb-darmstadt/shacl-form/plugins/leaflet.js'
const form = document.getElementById("shacl-form")
form.registerPlugin(new LeafletPlugin({ datatype: 'http://www.opengis.net/ont/geosparql#wktLiteral' }))When a SHACL property has datatype http://www.opengis.net/ont/geosparql#wktLiteral, the plugin renders the editor/viewer elements. This plugin uses Leaflet to edit or view geometry in well known text on a map.
Plugins can also be registered for a specific property path instead of a datatype:
form.registerPlugin(new MyPlugin({ predicate: 'http://example.org/title' }))In that case, predicate is matched against the SHACL property's sh:path. Plugin lookup first tries a matching predicate and datatype, then predicate only, then datatype only.
Custom plugins can be built by extending Plugin.
Property grouping and collapsing
Properties can be grouped using sh:group in the shapes graph. This example defines a group "Physical properties" and assigns certain properties to it.
When data-collapse is set, <shacl-form> creates an accordion-like widget that toggles grouped properties to reduce visual complexity. If the grouped properties should start open, set data-collapse="open".
In addition, all properties with sh:node and sh:maxCount != 1 are collapsed.
Supported RDF formats
Input formats
- text/turtle, application/n-triples, application/n-quads, application/trig using the N3 parser
- application/ld+json using jsonld
- application/rdf+xml using rdfxml-streaming-parser
Output formats
- text/turtle, application/n-triples, application/n-quads, application/trig using the N3 writer
- application/ld+json using jsonld
Theming
<shacl-form> has a built-in abstraction layer for theming the form controls. To use another theme (e.g. Bootstrap or Material Design), extend Theme and call setTheme() on the element.
If you only want to restyle the existing widgets (without re-implementing internal behavior), you can use CSS in two ways:
- Render into light DOM for global CSS:
<shacl-form data-use-shadow-root="false"></shacl-form>- Use CSS variables and parts (works with Shadow DOM too). The following CSS variables are supported:
shacl-form {
--shacl-font-family: system-ui, sans-serif;
--shacl-font-size: 14px;
--shacl-text-color: #333;
--shacl-muted-color: #555;
--shacl-border-color: #ddd;
--shacl-bg: #fff;
--shacl-row-alt-bg: #f8f8f8;
--shacl-error-color: #c00;
--shacl-label-width: 10em;
}And these parts are exposed for styling:
shacl-form::part(form) { padding: 8px; }
shacl-form::part(field) { gap: 6px; }
shacl-form::part(label) { font-weight: 600; }
shacl-form::part(editor) { border-radius: 6px; }
shacl-form::part(button) { min-height: 32px; }
shacl-form::part(primary) { font-weight: 700; }
shacl-form::part(add-button) { }
shacl-form::part(remove-button) { }
shacl-form::part(link-button) { }
shacl-form::part(submit-button) { }Available parts:
form, node, linked-node, node-title, group, group-title, collapsible, property, property-instance, field, label, editor, lang-chooser, constraint, constraint-editor, add-controls, remove-controls, add-button, remove-button, link-button, submit-button, button, primary.
Note: the default widgets are provided by ULB Darmstadt. Those components expose their own part names; you can style them via ::part(...) selectors on the respective elements. See the README for documentation.
Use with Solid Pods
<shacl-form> can be integrated with Solid Pods. Because toRDF() returns an RDF/JS N3 Store (see above), it can be passed to the Solid client fromRdfJsDataset() function to convert it into a Solid Dataset. The example below (based on Inrupt's Solid Pod tutorial) shows how to merge data from a <shacl-form> with a Solid data resource at readingListDataResourceURI:
// Authentication is assumed, resulting in a fetch function able to read and write into the Pod
try {
// Get data out of the shacl-form
const form = document.querySelector('shacl-form')
// Extract the RDF graph from the form
const shaclFormGraphStore = await form.toRDF()
// Convert RDF store into a Solid dataset
const shaclFormDataset = await fromRdfJsDataset(shaclFormGraphStore)
// First get the current dataset
myReadingList = await getSolidDataset(readingListDataResourceURI, { fetch: fetch })
// get all things from the shaclFormDataset
const shaclFormThings = getThingAll(shaclFormDataset)
// add the things from ShaclForm to the existing set
shaclFormThings.forEach((thing) => (myReadingList = setThing(myReadingList, thing)))
// save the new dataset
let savedReadingList = await saveSolidDatasetAt(readingListDataResourceURI, myReadingList, {
fetch: fetch
})
// Other handling here
} catch (err) {
console.error(`Storing SHACL data from Form failed with error ${err}!`)
}