Excel viewer component for React, Vue and Angular

Sheetwarden is framework-agnostic: you give it an element and it renders inside a Shadow DOM. Wrapping it in a component takes a few lines in any framework. The examples below mount a viewer, load a file and clean up on unmount.

React

import {{ useEffect, useRef }} from 'react';
import {{ createExcelViewer }} from '@sheetwarden/viewer';

export function ExcelViewer({{ file, readOnly = true, onChange }}) {{
  const host = useRef(null);
  const viewer = useRef(null);

  useEffect(() => {{
    viewer.current = createExcelViewer(host.current, {{
      readOnly, onChange,
      licenseKey: import.meta.env.VITE_SHEETWARDEN_KEY,
    }});
    return () => viewer.current?.destroy();
  }}, [readOnly]);

  useEffect(() => {{ if (file) viewer.current?.loadFile(file).catch(() => {{}}); }}, [file]);

  return <div ref={{host}} style={{{{ height: '75vh' }}}} />;
}}

The viewer shows load errors itself, so the catch only prevents an unhandled rejection. Keep onChange stable with useCallback if you include it in the dependency list.

Vue 3

<script setup>
import {{ onMounted, onBeforeUnmount, ref, watch }} from 'vue';
import {{ createExcelViewer }} from '@sheetwarden/viewer';

const props = defineProps({{ file: File }});
const host = ref(null);
let viewer;

onMounted(() => {{ viewer = createExcelViewer(host.value, {{ readOnly: true }}); }});
watch(() => props.file, file => {{ if (file) viewer.loadFile(file).catch(() => {{}}); }}, {{ immediate: true }});
onBeforeUnmount(() => viewer?.destroy());
</script>

<template><div ref="host" style="height: 75vh"></div></template>

Angular

import {{ Component, ElementRef, Input, OnChanges, OnDestroy, ViewChild, AfterViewInit }} from '@angular/core';
import {{ createExcelViewer, ExcelViewer }} from '@sheetwarden/viewer';

@Component({{ selector: 'app-excel-viewer', template: '<div #host style="height:75vh"></div>' }})
export class ExcelViewerComponent implements AfterViewInit, OnChanges, OnDestroy {{
  @Input() file?: File;
  @ViewChild('host', {{ static: true }}) host!: ElementRef<HTMLDivElement>;
  private viewer?: ExcelViewer;

  ngAfterViewInit() {{ this.viewer = createExcelViewer(this.host.nativeElement, {{ readOnly: true }}); this.load(); }}
  ngOnChanges() {{ this.load(); }}
  ngOnDestroy() {{ this.viewer?.destroy(); }}
  private load() {{ if (this.file && this.viewer) this.viewer.loadFile(this.file).catch(() => {{}}); }}
}}

Notes for every framework

  • Give the host a height. The viewer fills its container.
  • One viewer per element. A Shadow DOM cannot be detached, so create a new element when you need a new viewer.
  • Styles don't leak in either direction, so your CSS reset and theme have no effect inside the viewer and its styles have none outside.
  • Server-side rendering: create the viewer in a browser-only lifecycle hook; it uses window, Web Workers and Canvas.
  • Bundlers: Vite, webpack 5, Rollup and esbuild all emit the worker asset automatically. Next.js and Nuxt work with dynamic import on the client.

Beyond display

Turn readOnly off to let users edit, then read the model from onChange or call exportXlsx() for a file to upload. Open xlsm files and call enableVba() to run macros. The full API is in the documentation.

Related guides

Try it now: open the live demo and drop in one of your own files. Nothing is uploaded. Then see the documentation or pricing.