Using the Web Components UI
HOOPS Communicator 2025.3.0 brings support for custom Web Components to HOOPS Web Viewer. These components encapsulate the main features of HOOPS Web Viewer as HTML tags. These include the viewer itself and the model tree. There are also additional Web Components based on React which simplify UI implementation in your application.
Note
The Web Components implementation is currently in a beta state.
To demonstrate this feature, the HOOPS Communicator package also includes a new HWV demo app. This is a sample React application that leverages the HOOPS Web Components. This is not an officially supported part of the SDK - rather, this is a sample application (partners wishing to use or adapt this for their own use must own the maintenance of their source code).
Here, we’ll demonstrate how to get it working in the simplest way using JavaScript, npm, and vite (instructions for installing and using npm and vite are beyond the scope of this document). Follow these instructions to get it working on your system:
Minimal setup
- Create a new vite project using
npm create vite@latest
. In the vite install script, follow the prompts to enter a name for the app, choose “Vanilla”, then JavaScript. - After the project is created, run
npm install
andnpm run dev
. Note the localhost address that will be provided to you by Vite.

- Delete index.html, as well as all the files in the folders that are created (you can keep the folders themselves).
- In the root directory, create a folder called @hoops and a new index.html.
- Locate COMMUNICATOR_INSTALL_DIR/web_viewer/@hoops and copy all folders (except beta-demo-app-src) to your new @hoops folder.
- Run
npm install "./@hoops/web-viewer-components"
- Copy any .scs file to the public folder of this new project. Sample files can be found in COMMUNICATOR_INSTALL_DIR/quick_start/converted_models/standard/scs_models.
- Inside your new index.html, create a basic HTML structure and add the following code to the
<body>
:
<hoops-web-viewer
id="hwv"
endPointUri="./microengine.scs"
enginePath="./@hoops/sc-engine">
</hoops-web-viewer>
<script type="module">
import '@hoops/web-viewer-components';
</script>
- For the
endPointUri
value, replace the filename with the .scs file you copied to the public directory.
After navigating to the address generated by vite in step 2, you should see a model in your browser.
Adding a button
The new HOOPS UI comes with a few predefined Web Components. We’ll start with a simple icon button.
Add the following HTML to the body of index.html:
<div id="hwvHomeButton" style="position:absolute; top: 10px; left: 10px;">
<hoops-icon-button>
<hoops-icon icon="home"></hoops-icon>
</hoops-icon-button>
</div>
This code shows the button on the screen, but it doesn’t do anything yet. Add the following JavaScript inside the <script>
tag (after the import
statement):
document.getElementById("hwv").addEventListener("hwvModelStructureReady", (e)=>{
const { hwv } = e.detail;
document.getElementById("hwvHomeButton").addEventListener("click", ()=>{
hwv.reset();
});
});
You should now have a working “Home” button. Clicking this button will rotate the model back to its default position.

Customizing the appearance
Create a <style>
tag in the index.html file and add the following to make the viewer appear full screen:
<style>
body {
margin: 0 auto;
}
hoops-web-viewer {
width: 100vw;
height: 100vh;
}
</style>
To change the appearance of the hoops-icon, you can customize the CSS variables that define its behavior, such as the stroke color. In the <style>
element, add the following before the body:
:root {
--hoops-svg-stroke-color: blue;
}
Using the model tree
The model tree is a HOOPS component that displays the assembly structure and its attributes hierarchically. It can be expanded and collapsed, and individual component visibility may be toggled. The model tree is represented as a web component. The associated HTML tag is <hoops-model-tree>
. To use it, you’ll need to add the tree itself as well as its associated UI elements to your application. First, the model tree itself, along with an id
:
<hoops-model-tree id="hwvModelTree"></hoops-model-tree>
This positions the component and loads the model tree into memory, but it’s not visible because visibility is disabled by default. Let’s go through the process of adding the necessary logic to make it functional. We’ll plan to toggle the visibility on and off with a button. There is already a HOOPS Web Component for this button, the <hoops-toolbar-model-tree>
tag. Add the following code to your file for the toggle button:
<div id="ModelTree" style="position: absolute; top: 40px; left: 10px; z-index: 2;">
<hoops-toolbar-model-tree id="hwvModelTreeButton"></hoops-toolbar-model-tree>
</div>
In your <script>
tag, add the following import statement and Boolean variable:
import { SelectionMode } from '@hoops/web-viewer';
let modelTreeVisible = false;
Then, in the hwvModelStructureReady
callback, your code should look like the following:
document.getElementById("hwv").addEventListener("hwvModelStructureReady", (e) => {
// logic to toggle the visibility of the model tree
document.getElementById("hwvModelTreeButton").addEventListener("click", () => {
if (modelTreeVisible) {
document.getElementById("hwvModelTree").style.display = "none";
}
else {
document.getElementById("hwvModelTree").style.display = "block";
}
modelTreeVisible = !modelTreeVisible;
});
});
This will enable the application to show and hide the model tree UI when you click the associated model tree button. The model tree can be expanded and collapsed, but it doesn’t do much else. Let’s expand on the previous code snippet to add further logic. Add this to the hwvModelStructureReady
block:
document.getElementById("hwvModelTree").model = hwv.model;
const hwvModelTreeElement = document.getElementById("hwvModelTree");
hwvModelTreeElement.addEventListener("hoops-model-tree-node-click", (e)=> {
e.stopPropagation();
hwv.selectionManager.clear(false);
hwv.selectionManager.selectNode(e.detail.nodeId, SelectionMode.Add);
});
Notice the call to selectNode
. This is what enables you to click a component in the model tree and automatically select the associated part in the model. Next, we’ll enable the show/hide icon at the end of each line of the model tree. Continuing from the code above:
hwvModelTreeElement.addEventListener("hoops-model-tree-node-visibility-change", (e)=> {
e.stopPropagation();
hwv.model.setNodesVisibility([e.detail.nodeId], e.detail.visibility);
hwvModelTreeElement.updateNodeData(e.detail.nodeId, {
visible: e.detail.visibility
})
});
Now, the model is selectable and its parts can be shown and hidden.
Note
You need to update the tree if it changes from the Web Viewer. To update the entire tree, you can simply reassign the model to the tree. The underlying framework will detect a change in the model and update the tree automatically.

HOOPS layout
The UI kit comes with a layout that can be used for common web page layouts.
The <hoops-layout>
tag is a meta-component that may contain other components through slots, which are named as such: <menu-bar>
, <toolbar-top>
, <panel-top>
, <toolbar-left>
, <panel-left>
, <central-widget>
, <panel-right>
, <toolbar-right>
, <panel-bottom>
, <toolbar-bottom>
, and <status-bar>
.
To implement this in the code, you have to instantiate a hoops-layout
, create a component inside of it, and have its slot set to one of the above options. Your component should be placed accordingly in the layout. The code below illustrates how this works:
<HoopsLayout floatingPanels>
<AppHeader
versions={viewerState}
sessionName={props.sessionName ?? scsModel ?? 'untitled'}
infoShown={uiState.infoShown}
onInfoToggled={(shown) => uiActor.send({ type: 'setInfoShown', shown })}
onOpenEmpty={() => viewerActor.send({ type: 'openEmptyViewer' })}
onOpenModel={(modelName) => viewerActor.send({ type: 'openModel', modelName })}
onImportModel={(modelName) => viewerActor.send({ type: 'importModel', modelName })}
onImportLocalModel={async (file: File) =>
viewerActor.send({ type: 'importLocalModel', file })
}
/>
<Toolbar />
<LeftPanel />
<div slot="central-widget" ref={centralWidgetRef}>
<WebViewerComponent
className="viewer"
empty={!scsModel}
endpointUri={scsModel ? `${scsModel}` : undefined}
enginePath="."
hwvReady={hwvReady}
hwvAssemblyTreeReady={() => viewerActor.send({ type: 'assemblyTreeReady' })}
hwvSceneReady={() => viewerActor.send({ type: 'sceneReady' })}
hwvModelStructureReady={hwvModelStructureReady}
hwvSelectionArray={hwvSelectionArray}
onClick={() => {
uiActor.send({
type: 'setContextMenuShown',
shown: false,
});
}}
onAuxClick={(e) => {
uiActor.send({
type: 'setContextMenuShown',
shown: true,
position: { x: e.clientX, y: e.clientY },
});
}}
/>
<ContextMenu />
</div>
<RightPanel />
</HoopsLayout>
In this example, the AppHeader
component is set to the menu-bar:
function AppHeader(props: AppHeaderProps) {
return (
<header
data-testid="menu-bar"
slot="menu-bar"
style={{
position: 'relative',
paddingRight: '0.5rem',
}}