diff --git a/src/index.tsx b/src/index.tsx
index e7f946d6..3651ea88 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -23,6 +23,7 @@ export interface VizarrViewer {
   addImage(config: ImageLayerConfig): void;
   setViewState(viewState: ViewState): void;
   on<E extends keyof Events>(event: E, cb: (data: Events[E]) => void): void;
+  destroy(): void;
 }
 
 /** switch to Promise.withResolvers when it's available */
@@ -44,12 +45,21 @@ export function createViewer(element: HTMLElement): Promise<VizarrViewer> {
     atom<ViewState | undefined>(undefined),
     ({ zoom, target }) => emitter.emit('viewStateChange', { zoom, target })
   );
-  let { promise, resolve } = defer<VizarrViewer>();
+  const { promise, resolve } = defer<VizarrViewer>();
 
   function App() {
     const addImage = useSetAtom(addImageAtom);
     const setViewState = useSetAtom(viewStateAtom);
-    React.useImperativeHandle(ref, () => ({ addImage, setViewState, on: emitter.on }), []);
+    React.useImperativeHandle(
+      ref,
+      () => ({
+        addImage,
+        setViewState,
+        on: emitter.on,
+        destroy: () => root.unmount(),
+      }),
+      []
+    );
     React.useEffect(() => {
       if (ref.current) {
         resolve(ref.current);
@@ -62,13 +72,13 @@ export function createViewer(element: HTMLElement): Promise<VizarrViewer> {
       </>
     );
   }
-  ReactDOM.createRoot(element).render(
+  let root = ReactDOM.createRoot(element);
+  root.render(
     <ThemeProvider theme={theme}>
       <Provider>
         <App />
       </Provider>
     </ThemeProvider>
   );
-
   return promise;
 }