npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ooxml-ui

v0.20.0

Published

Shared, format-neutral web components and styles for the Office viewers (Word, PowerPoint, Excel)

Downloads

5,179

Readme

ooxml-ui

npm version license types

Shared Office controls, styles and custom elements for browser applications.

Try the apps | npm | Full docs | Source

Shared, format-neutral web components and styles for the Office viewers (Word, PowerPoint and Excel). Vanilla custom elements with shadow-root controls, typed events, no framework and no runtime dependency except ooxml-core (types and a few pure helpers).

You do not install this package. It is a regular dependency of every published editor package (docx-*-viewer, pptx-*-viewer and xlsx-*-viewer), so installing the editor of your framework pulls it in. It is published separately only so that the editors share one copy of the controls. Do not add it to your own install instructions or package.json; import controls through the editor package, which registers them for you. Importing ooxml-ui directly is for the viewer packages themselves and for people building their own Office-style UI.

Install

Applications using a viewer receive this package as a dependency. For a custom Office UI, install it directly:

npm install ooxml-ui

Quick start

import { registerOfficeUi } from 'ooxml-ui';

registerOfficeUi();
const button = document.createElement('office-ui-button');
button.setAttribute('label', 'Save');
button.setAttribute('command', 'save');
button.addEventListener('office-command', (event) => console.log(event.detail));
document.body.append(button);

API: entries (ESM only)

| Entry | Contents | | ------------------- | ------------------------------------------------------------------------------------------------------------ | | ooxml-ui | everything below plus registerOfficeUi() and OFFICE_UI_TAGS | | ooxml-ui/theme | THEME_CSS, installOfficeUiTheme() (tokens, dark mode, forced-colors, touch targets) | | ooxml-ui/icons | icon registry (registerIcon, getIcon, listIcons) and <office-ui-icon> | | ooxml-ui/controls | button, checkbox, switch, select, ribbon group, toolbar, dialog, status bar and item, zoom slider, tab strip | | ooxml-ui/presence | <office-ui-presence> avatar stack for collaboration awareness | | ooxml-ui/smartart | <office-ui-smartart>: draws a core DiagramDrawing as SVG |

Every entry declares an import and a default export condition, so CommonJS consumers on Node 20.19+ or 22.12+ load it through require(esm).

Importing an entry never touches the DOM (SSR-safe). Elements are defined by the define* / register* functions, which are idempotent, no-ops without a window, and refuse to coexist with an incompatible build of this package.

Features: elements

| Tag | Attributes / properties | Events (all bubble, composed) | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | office-ui-button | label, icon, command, disabled, pressed, expanded, icon-only, variant="stacked", keyshortcuts | office-command { command } | | office-ui-checkbox, office-ui-switch | checked, disabled, value, aria-label; form-associated | input, change | | office-ui-select | <option> / <optgroup> children (data-display-label, data-description, font previews) or the options property [{ value, label, disabled? }]; value, selectedIndex, disabled, aria-label, variant="ribbon-font" | "ribbon-icon", data-font-picker; iconandcustom slots; arrows, Home, End, PageUp/PageDown, typeahead | input, change | | office-ui-ribbon-group | label, launcher (dialog launcher command), launcher-disabled; default slot | launcher: office-command { command } | | office-ui-ribbon-stack | orientation="horizontal"; default slot (three-row column of small commands by default) | none | | office-ui-menu-button / -menu-item | button: label, icon, command (split button), variant, disabled, keyshortcuts; item: command, label, icon, disabled, checked | item choice: office-command { command } | | office-ui-toolbar | aria-label, aria-orientation; arrow-key focus movement | none | | office-ui-dialog | open, heading, dismissible="false", show(), close(); slots default, footer | office-dialog-close { reason } (cancelable) | | office-ui-status-bar / -status-item | label, value, interactive, id; controlled bar: state { items?, toggles?, views?, zoom? } with collaboration and end slots | office-status-activate { id } | | office-ui-zoom-slider | value (percent), min, max, step, disabled, fit (fit button name), label | input, change; fit: office-command { command: 'zoom-fit' } | | office-ui-tab-strip | tabs [{ id, label, title? }], selected, label, previous-label, next-label, add-label, add-disabled, add-title, disabled | office-tab-select { id } (cancelable); add: office-command { command: 'tab-add' } | | office-ui-context-menu / office-ui-menu-separator | Children mode: label, openAt(x, y), close(), open over office-ui-menu-item and separator children. Controlled mode: state { x, y, label, items: [{ id, label, separatorBefore?, heading?, danger?, disabled?, checked?, icon? }], markers?, zIndex?, autoFocus? }; arrows, Home, End and type-ahead in both | children: item office-command { command }; controlled: office-menu-request { id }, office-menu-close { reason } (never closes itself) | | office-ui-command-search | commands [{ id, label, keywords?, description?, disabled?, title? }], placeholder, label, limit (Office's "Tell me what you want to do") | choice: office-command { command } | | office-ui-options-dialog | categories [{ id, label, description?, disabled?, sections: [{ id, title, controls }] }] (controls: toggle, select, number, text, each optionally disabled with a reason), values, category, open, heading, show(), close() | OK: office-options-change { values, changed } | | office-ui-account | profile { displayName, avatarColor, initial? }; slots sign-in and default (product sections) | office-profile-change { profile } | | office-ui-ribbon | Tab row over tab panels. Panels are children with data-ribbon-tab, data-label, optional data-tab-keytip; slots quick-access, search, end; selected, label, file-label, no-file, file-expanded, file-keytip; focusFile(), focusTab() | office-ribbon-select { tab } (cancelable), office-ribbon-file | | office-ui-backstage | items [{ id, label, group?: 'footer', disabled?, title? }], open, selected, label, back-label, show(page?), close(); pages are children with data-backstage-page | office-backstage-select { id } (cancelable), office-backstage-close { reason } (cancelable) | | office-ui-find-bar | open, label, input-label, placeholder, maxlength, disabled, navigation-disabled, previous-label, next-label, close-label; value, status, statusTitle, show(), close() | office-find-input { query }, office-find-step { direction }, office-find-close | | office-ui-ruler | orientation="vertical", origin (px offset of zero), scale (px per unit, default 96), direction="reverse"; decorative | none | | office-ui-print-preview | pages (rendered nodes; an inert clone of the current one is shown), index, label, empty-label | office-print-preview-page { index } | | office-ui-radio | checked, disabled, value, name; radios of one tag sharing a name form a roving-focus group; form-associated | input, change | | office-ui-search | value, placeholder, disabled, aria-label, variant="titlebar"; focus(), select() | input, change | | office-ui-ribbon-toggle | label, checked, disabled, title, command (a labelled ribbon checkbox row; static checkboxTag picks the inner checkbox) | office-toggle { command, checked } | | office-ui-dialog-footer | state { actions: [{ id, label, variant?, icon?, disabled?, busy?, title?, align?, testId? }] }, focusAction(id) | office-dialog-footer-request { id } | | office-ui-toasts | state { toasts: [{ id, code?, severity, message }], overflowCount?, labels? }; hidden when empty, host positions it | office-toasts-request { id: 'dismiss', toastId } or { id: 'dismissAll' } | | office-ui-read-only-banner | state { kind, message, passwordPromptOpen?, passwordError?, checkingPassword?, labels? } | office-read-only-request (Edit anyway, Dismiss, password submit or cancel) | | office-ui-paste-options | state { left, top, options: [{ id, label }], label? }; fixed 4px past the pasted object | office-paste-options-request { format }, office-paste-options-dismiss | | office-ui-title-bar | state { appMark?, fileName, status?, tone?, autosave?, quickAccess?: { label, items, showLabels? }, search? }, placement (titleBar, belowRibbon); slots collaboration, account; tokens --office-title-bar-* | office-autosave-toggle, office-command { command }, office-command-search { query, command? } | | office-ui-ribbon-section | groups [{ id, label, commands: [{ id, label, icon, title?, size?, badge?, disabled?, active?, compact?, hidden?, caret?, pressed?, expanded?, column? }] }]; keyed groups and commands in the light DOM | the commands' own events (office-command) | | office-ui-gallery | state { id, label, moreLabel?, disabled?, command?, sections: [{ title?, columns, tileWidth, tileHeight, items: [{ id, label, applied?, preview? }] }], inline? }, mode (inline), chevron-only, icon; open, disabled, close(); tiles in light DOM (data-gallery-item), previews parsed by parseSvgPreview | office-gallery-pick { gallery, itemId } | | office-ui-presence | participants, max, label | office-presence-select { id } | | office-ui-smartart | drawing (core DiagramDrawing), schemeColors, label | office-smartart-render (render report) | | office-ui-icon | name, label | none |

The local profile behind office-ui-account is stored on the device only, under one suite-wide key (readOfficeProfile, writeOfficeProfile, clearOfficeProfile), so every Office viewer on an origin shows the same name and avatar colour.

attachKeyTips(scope) (from ooxml-ui/controls) adds Office KeyTips to any element or shadow root: press and release Alt to show badges on controls marked data-keytip="H", type a badge to activate it (controls inside open shadow roots of shared elements, such as ribbon tabs, are included), and use data-keytip-panel / data-keytip-level for a tab's second level. Escape steps back; disabled controls are dimmed and never run.

Elements with intent events read their event name and test-id prefix from static class fields (requestEvent, testIdPrefix), so a product can register a subclass under its own tag that keeps its published contract.

Setting a property never emits an event; only user activation does. Controls honour prefers-color-scheme, forced-colors: active (system colours and a visible focus ring) and coarse pointers (44px targets through --office-target-size).

Design tokens

Every control is token based: no colour, length, radius, font size, shadow, duration or layer is written literally. OFFICE_TOKENS (from ooxml-ui/theme) is the single table of --office-* tokens and their defaults; controls read them through tok(name), which expands to var(--office-…, <default>), so they render correctly with or without the installed theme. THEME_CSS declares every default on :root, plus dark-mode and forced-colours values. Override any token on :root or on any container to retheme or resize the suite:

  • semantic colours: --office-foreground, -background, -surface, -selected, -border, -accent, -accent-foreground, -ring, -danger, -warning, -info, -popover, -overlay, -notice-*, -paper;
  • scales: --office-space-*, --office-radius-*, --office-font-size-*, --office-icon-size-*, --office-control-height-*, --office-shadow-*, --office-duration-*, --office-z-*;
  • component tokens that default to the scales, for example --office-checkbox-size, --office-switch-width, --office-ribbon-tab-height, --office-backstage-nav-width and --office-dialog-max-width.

Only the two layout breakpoints (COMPACT, TOUCH) are literal, because CSS does not allow custom properties in media queries. A test fails when any control's stylesheet contains a raw colour or length outside a token, or references an undeclared token.

office-ui-smartart shows the drawing the producing application cached; it never lays out a diagram itself. Preset outlines it cannot draw are rendered as rectangles, gradient and pattern fills are approximated, and 3D is flattened. All of that is listed in the office-smartart-render event detail (approximatedGeometries, approximatedFills, flattened3d); nothing is hidden.

Scope

Only what every Office product needs lives here. Product-specific ribbon content (PowerPoint slides, transitions, animations; Word styles and review) stays in the viewers. See docs/ooxml-ui-plan.md in the repository for what moves when, and how the pptx-ui-* tags become aliases of office-ui-*.

Development

bun install
bun run --cwd packages/ui typecheck
bun run --cwd packages/ui test
bun run build && bun run --cwd packages/ui build   # core first: the declarations read ../../dist
bun run --cwd packages/ui test:package

Documentation

Core and UI source | Shared UI plan

License

Apache-2.0. Several controls are derived from pptx-viewer; see PROVENANCE.md at the repository root.