Library / SDK
ProjectMirador/mirador avatar
ProjectMirador/mirador

Mirador: a multi-up viewer for images, in the browser

An open-source, web-based 'multi-up' viewer that supports zoom-pan-rotate functionality, ability to display/compare simple images, and images with annotations.

620 stars282 forksJavaScriptApache-2.0

At a glance

What is it?
Mirador is the viewer you open when you want to compare two images side by side, or look at one image with annotations on it, without leaving a web page. It is a JavaScript application built on a deep zoom library, it speaks the international image interoperability format, and it can be embedded in a page with a script tag or used as a component in a larger application.
Who is it for?
Mirador fits a library, a museum, or a research group that wants to put images in front of people without building a viewer, and a developer who needs annotation and comparison in an existing application. It does not fit a project that wants to display a single image and nothing else, because the whole application is built around the window.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the thing is for

The project description is precise: an open source, web based multi-up viewer that supports zoom, pan and rotate, the ability to display and compare simple images, and images with annotations.

The word multi-up is the key one and it is a term from the print world, where a multi-up page is a single sheet carrying several images. That is exactly what this is: a workspace of windows, each showing an image, that you can arrange and compare. The comparison is the point. Looking at two photographs in two separate browser tabs tells you very little; looking at them in two aligned windows with the same zoom and the same pan tells you a great deal, and that is a feature that a plain image tag cannot provide.

The three verbs in the description are the interaction model. Zoom, pan and rotate are the three things you do to an image, and a viewer that gets all three right feels like handling a physical print. Rotate in particular is the one that is often missing and always wanted, because archival material is rarely photographed square.

And the third item, images with annotations, is what turns a viewer into a scholarly tool. An annotation is a region of an image with text attached to it, and being able to place them, move them, and save them alongside the image is the difference between a gallery and a research environment.

The topics the repository carries name the two standards underneath: the international image interoperability format for the images themselves, and the deep zoom library that does the viewing. So Mirador is not a general image viewer; it is a viewer for images served in that interoperable form, which is the format museums and libraries have standardised on.

The project is Apache 2 licensed, the site is projectmirador.org, the last push was on 2026-10-16, and there are three releases in the fourth series through the autumn of 2026.

Installing it, and the version warning

The installation section is short, and it splits into two audiences.

For most people the recommendation is a JavaScript package manager, and there are two commands for the two of them.

sh
$ npm install mirador 

# or

$ yarn add mirador

For a project that wants to integrate Mirador with plugins, the readme recommends a specific bundler for the module version of the packages, and points at a separate repository with worked examples.

And for a page that simply wants to embed the viewer with no further customisation, there is the universal build, included with a script tag.

html
<script src="https://unpkg.com/mirador@latest/dist/mirador.min.js"></script>

And then the warning, which is the most useful line in the whole readme and the kind of note that saves a production incident.

The latest tag will at some point switch from version three to version four. If you use Mirador through a content delivery network in a production environment, the readme says to consider pinning to the third version to avoid sudden breaking changes.

html
<script src="https://unpkg.com/mirador@^3/dist/mirador.min.js"></script>

So the advice is to pin, and to pin the older major version while the migration is on your own schedule. The reasoning is worth noting: a floating tag in a content delivery network means the page you tested in the morning is not the page your users get in the afternoon, and with a major version change that difference is a blank page rather than a styling difference.

The rest of the embedding documentation lives in the wiki, with a page on embedding the viewer in a document with JavaScript, and the readme also points at a directory of translations and the instructions for adding one.

That last point is worth noting for an open source project with an interface: the translations are contributed, and the readme links the instructions for adding one, which is the difference between a project with a Chinese or Japanese interface because a maintainer speaks that language and one that has it because somebody contributed it.

The state model, and how a plugin talks to it

The development section explains how the application is put together, and it starts with the instantiation.

javascript
var miradorInstance = Mirador.viewer({
  id: 'mirador' // id selector where Mirador should be instantiated
});

> miradorInstance
{ actions, store }

The returned object has two things in it, the actions and the store. That is the whole public interface of the application, and it tells you the architecture: the state lives in a store, and everything that changes it goes through the actions.

The examples make that concrete. Adding a window is a dispatch of the add window action. Focusing an existing window is a dispatch of the focus action with a window identifier. And reading the current state is a call to get the state on the store.

javascript
store.dispatch(actions.addWindow());
javascript
store.dispatch(actions.focusWindow('window-1'))
javascript
store.getState()

This is a well known shape and it is the right one for a viewer, because a viewer is exactly the kind of application where the interface is a projection of a state tree and where an embedder needs to be able to drive it. A page that wants a button which opens a comparison view does not want to simulate clicks; it wants to dispatch an action. And a plugin that wants to know what the user is looking at reads the state.

The debug documentation is consistent with that. It recommends two browser extensions for working on a local instance, one for inspecting the component tree and one for inspecting the store, which is the tool that makes the state model legible while you are developing against it.

And the store is the reason the plugin story works. A plugin can add a window, or register a tool, or read the current state, without reaching into the component tree, because the interface is the state and the state has a documented shape. That is a much better basis for an extension ecosystem than a set of documented callbacks.

The fallback image, and the small decisions

Among the sections of the readme, one that looks like a minor feature is worth a paragraph, because it is a decision about what a user sees when something goes wrong.

Mirador automatically displays a simple fallback placeholder when images fail to load, and the fallback image can be customised through configuration.

javascript
const config = {
  fallbackImage: 'https://example.com/custom-fallback.jpg',
};

The error message is translatable through a named key, and the detailed error information is logged to the console for debugging.

Three things are happening there, and the combination is deliberate. The user gets something rather than a broken image frame. The message is in their language. And the developer gets the real error, but in the console rather than on the screen.

That last part is the interesting design choice. A viewer for archival material is often pointed at a server that has moved, a manifest that has changed, or an image that is behind an access control the viewer does not know about, and in all three cases the user is not the person who can fix it. So the interface shows a placeholder and a friendly message, and the person who can fix it finds the detail in the console. Splitting the two audiences is what makes the feature right rather than merely polite.

The demo directory shows the same thinking applied across the feature set, with a separate page for the fallback behaviour and for the other cases: the collections view, the content search, the internationalisation, the embedding in a frame, the third version of the image format, the layers view, a minimal configuration, several viewers on one page, the plugin examples with a companion window and with a priority, the plugin state and validation examples, the right to left layout, the sequence switching, a single manuscript, a video, the table of contents, and the annotations drawn as vector graphics.

That demo directory is the documentation of the project in practice. Twenty-eight pages, each one a small feature isolated, and a reader can see what the viewer does with any single capability without reading any code.

The build, the checks, and the size limit

The package manifest is where the engineering discipline shows.

The build is two bundler invocations, one for the main configuration and one for the universal build configuration, which is the standard way to produce a module build and a script tag build from one source tree.

The lint command is not one command. It runs the linter over the project, and then two custom scripts, one that checks the translations and one that checks the containers. Both of those are custom checks that a general linter cannot do, and both are about consistency: the translation files have to have the same keys as each other, and the component containers have to follow a rule that the project has decided on.

The size limit is the other interesting entry, because a viewer that embeds an image library is exactly the kind of package where a size budget matters, and the manifest has a configuration file for it at the top of the repository. A library with a stated size limit is one where the maintainers have decided what the cost of using it is and have made it checkable.

The test command ties the whole thing together, and the order is deliberate.

code
"test": "npm run build && npm run lint && npm run size && vitest run"

Build, then lint, then the size check, then the tests. So a change that makes the bundle too large fails the suite even if every test passes, and a change that breaks the translation files fails before the tests even run. The readme also notes that the linter and the size check can be skipped when you want to run the test runner alone, and that a single test file can be run directly.

The test runner is Vitest, and it has a graphical interface mode, with the flag passed through the package manager's own argument syntax. There is a separate debug script that starts a Node inspector client with breakpoints, and the readme links a walkthrough of using the built in debugger with the browser's developer tools.

The rest of the repository is conventional and thorough: the test directory, the demo pages, the source, a separate setup file for the tests, two bundler configurations, the test runner configuration, the formatting configuration and its ignore file, the linter configuration, the coverage configuration, a network configuration for the hosting of the site, and the scripts that hold the two custom lint checks.

Editorial conclusion

Mirador fits a library, a museum, or a research group that wants to put images in front of people without building a viewer, and a developer who needs annotation and comparison in an existing application. It does not fit a project that wants to display a single image and nothing else, because the whole application is built around the window. Verify first which version you are embedding, since the readme warns that the latest tag will move from the third major version to the fourth and that a production page should be pinned, that the annotations you load are in the format the viewer expects, and that the plugins you want are compatible with the version you install.

Frequently asked questions

What is the Mirador viewer?

It is an open source, web based multi-up viewer: a workspace of image windows you can arrange and compare, with zoom, pan and rotate, support for images with annotations, and support for the international image interoperability format. It is aimed at libraries, museums, and research projects rather than at single image display.

How do I embed Mirador in a page?

Include the universal build with a script tag, or install the package and use the module build with a bundler, where the readme recommends using vite and links a separate repository with worked integration examples. The wiki has more on embedding it in a document with JavaScript.

Should I pin the Mirador version when using a CDN?

Yes. The readme warns that the latest tag will at some point move from version three to version four, and that a production page should be pinned to the third version to avoid a sudden breaking change, since a floating tag means the page you tested is not the page your users get.

How does a Mirador plugin interact with the application?

Through the state and the actions. Instantiating the viewer returns an object with an actions object and a store, so a plugin adds a window by dispatching an action, focuses one by dispatching a focus action with its identifier, and reads the current state from the store. That is what makes the extension ecosystem possible without reaching into the component tree.

What happens when an image fails to load in Mirador?

The viewer shows a simple fallback placeholder instead of a broken frame, the error message is translatable through a named key, and the detailed error information is written to the console for debugging, so that the user sees something in their own language while the developer keeps the technical detail.

Official sources

  1. License: Apache-2.0
  2. ProjectMirador/mirador on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/projectmirador-mirador.svg)](https://hysenlabs.com/projects/projectmirador-mirador)

Was this useful?