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

@datocms/product-scenes

v0.5.0

Published

The product scenes of www.datocms.com: pictures of the DatoCMS interface, rendered on the server

Readme

@datocms/product-scenes

The pictures of the DatoCMS interface on www.datocms.com, made with the real components of the product and rendered to static HTML on the server.

This package is for the DatoCMS website only. It has no support and no semver promise: any version can change the API. The licence does not let you use it in other projects.

The package is a kit of parts: the frame (Stage), the parts of the screens of the cms (NavigationBar (with the tabs that plugins add, pluginTabs), ContentSidebar, RecordEditorLayout, VisualEditorLayout (the tab Visual, with the records of the page), WebPreview (the plugin Web Previews, with the page of the site and the outlines of content link), RecordForm (with the locale tabs of a localized record, the SEO field, the error of a field after a save, the external video field with its video (RecordForm.VideoInput, with the actions on the thumbnail if you give hovered), the file field with an image or with a media that the host plays (RecordForm.FileInput media, for example a video), the avatar and the lock of another user that edits the record, and the parts of a plugin: an outlet above the fields (RecordForm.Outlet) and the editor of a field (RecordForm.PluginInput), with the content from the website), ConfigurationMenu and PluginList (the menu of the environment configuration, and the plugins of the project in the list column, with the presets and the help of the title, and the pill of a private plugin), SeoPreviewStack (the SEO field in a stack of cards, one preview on each), StructuredTextInput, ModularContentField, InfoAside (with the stage of a record in a workflow and the revision history), RecordList (the collection of a model: its toolbar and the table of its records), RecordTree (the compact view of a model with hierarchical sorting: the tree of its records, in the list column of RecordEditorLayout), RolePermissions, the record permissions of a role (with the conditions on the creator and on the stage of a record), SelectivePublishing, ModelSettings and SeoDialog (the dialog of the SEO field, alone or over the record editor), three dialogs, WorkflowStages, the stages of a workflow in its settings, AddFieldMenu, the page of a model in the schema with the menu Add new field open, and the tab Schema: SchemaSidebar (the models and their API keys), ModelPage (the fields of a model) and FieldSettings (the dialog of an asset field, with its validations and the help text of its tab Presentation, or on its tab Settings with the switch of the localization, or the dialog of a text field with the toolbar buttons of its Markdown editor and the menu of its editors open), MediaArea (the media library: the tree of the collections, the grid of the assets, the menus of the saved filters and of the upload button) and AssetDetail (the details of an asset, alone or over the media library: the focal point and its crop previews, the asset info, the default metadata in each locale, the categorization; for a video, the player with the marker of the thumbnail (AssetDetail.Player, also alone), the panel of the audio tracks and the subtitles (AssetDetail.Tracks, with the tooltip of the download), and the dialogs Generate subtitles from audio (AssetDetail.GenerateSubtitles) and Add new track (AssetDetail.AddTrack, with the upload of the file), over the page or alone with over={false})) and a few components of the DatoCMS design system (Button, InfoList, Pill, from the package ui-public). The website composes the scenes from the parts and gives the fake data.

Install

npm install @datocms/product-scenes react@19 react-dom@19

The package has no dependencies: the build contains the components of the cms (the packages ui-public and ui-static of the DatoCMS monorepo). React 19 is a peer dependency.

The CSS and the Fonts

Import the CSS once, in the layout of the website:

import '@datocms/product-scenes/product-scenes.css';

Every rule of the file applies only inside a scene, thus the CSS of the page does not change. The file has no @font-face rule: the page gives the fonts that it already loads, with two custom properties on an element above the scenes.

The selects of the scenes also write rules when they render (react-select writes them with Emotion, in <style data-emotion> tags in the HTML). These rules are in the cascade layer vendor. When the website puts product-scenes.css in a layer, give the name of that layer to the cssLayer prop of Stage, thus the CSS of the kit beats these rules. A website without layers keeps the default: the CSS of the kit has no layer, thus it beats the layer.

:root {
  --product-scenes-font-sans: 'ABC Oracle', sans-serif;
  --product-scenes-font-mono: 'Geist Mono', monospace;
}

A dialog alone (SelectivePublishing, ModelSettings) has no page behind it: the screen is transparent, and the panel is at its top left, with a margin around it for its shadow. The margin is --spacing-xxl of the cms (about 56px); --product-scenes-dialog-margin, on an element above the scene, changes it. The panel takes the width of the screen less the margins, up to its width in the cms (600px or 700px): on a wider screen the space is on the right. The panel has the corners of the cms; --product-scenes-dialog-radius (in px of the page), on an element above the scene, changes them: a page that puts the panel at the corner of the frame (a margin of 0) gives it the radius of the frame.

SeoDialog with over is the dialog over the page: put it after the RecordEditorLayout, in the same Stage. It covers the screen with the backdrop of the cms, and the panel is near the top, in the center. --product-scenes-dialog-margin does not change it. A page that clips the right part of the screen gives that width, in px of the page, to --product-scenes-dialog-clip-end, on an element above the scene: the panel is then in the center of the part that shows, and narrower if that part is narrower than the panel.

The stack of SeoPreviewStack has no page behind it either: the screen is transparent, and the cards fill its width, 32px below its top edge, where the cards behind peek out.

A Scene

import {
  ContentSidebar,
  InfoAside,
  NavigationBar,
  RecordEditorLayout,
  RecordForm,
  Stage,
} from '@datocms/product-scenes';

export function PostEditorScene({ scheme }: { scheme: 'light' | 'dark' }) {
  return (
    <Stage description="The record editor of DatoCMS, with a blog post." scheme={scheme}>
      <RecordEditorLayout
        navigationBar={<NavigationBar user={{ name: 'Demo User' }} />}
        sidebar={
          <ContentSidebar
            items={[{ kind: 'model', id: 'posts', name: '📝 Posts', active: true }]}
          />
        }
        aside={
          <InfoAside>
            <InfoAside.RecordInfo
              id="Gd8Qnm7pRUKPjnCVhCYOZw"
              status="draft"
              createdAt="09/21/2025, 13:34"
              updatedAt="09/21/2025, 15:39"
              createdBy={{ name: 'Ronak Ganatra' }}
            />
          </InfoAside>
        }
      >
        <RecordForm title="Hello world" icon="📝">
          <RecordForm.Field label="Title" required>
            <RecordForm.StringInput value="Hello world" large />
          </RecordForm.Field>
        </RecordForm>
      </RecordEditorLayout>
    </Stage>
  );
}

In an Astro page, render the scene as a component, without a client: directive:

<PostEditorScene scheme="light" />

A model or a folder of ContentSidebar and the icon of RecordForm use the emoji of the model as the icon, as in the cms. A part that shows another icon takes it as a ReactNode.

The Stage

Stage is the frame of a scene. Its props:

  • description: what the scene shows, for a screen reader.
  • scheme: 'light' (default), 'dark', or 'page': the screen follows the scheme of the page (see "The scheme of the page").
  • hue: the hue of the brand color, from 0 to 360 (default 290).
  • tint: the strength of the hue on the surfaces and the borders (default 1). It multiplies the chroma of the neutral colors of the cms. In the dark scheme the hue barely shows: use 2 to 4 to make it show more. The text does not get it (the tint fades out on the stops of the text), thus the light text of the dark scheme stays almost white.
  • chroma: the most chroma of the brand color, in OKLCH (0 to about 0.3). It caps the signal colors of the cms: the primary buttons, the focus rings, the selected rows. Without it they are as vivid as the gamut allows, as in the cms (from 0.11 for a blue to 0.24 for a purple). Cap it where the accent of the cms must not win over the page around the scene: 0.08 is a quiet accent.
  • height: the height of the screen in px, before the zoom (default 900).
  • width: the width of the screen in px (see below).
  • minWidth: the smallest width of the screen of the cms in px (default 1025).
  • fit: the zoom makes the screen smaller on a stage narrower than the screen (default: true without width, false with width).
  • inspectable: for the development of the website only: the screen is not inert and not hidden, so the browser devtools can inspect it. Never in production: a screen reader would read the fake interface.

Without width, the screen takes the width of the stage, down to minWidth, as the cms takes the width of the window. On a narrower stage, the screen stays minWidth wide and CSS makes it smaller with zoom, thus it is exactly as wide as the stage. Give a larger minWidth to a scene that needs more space, for example a toolbar that must stay on one line.

With width, the screen is exactly that wide, and the stage is as wide as the screen at its zoom: the page can put the stage in a box with width: fit-content. Use it for a scene without RecordEditorLayout, for example a RecordForm alone in a narrow window. The screen does not get smaller: the page clips it. With fit, it gets smaller on a stage narrower than the screen.

The CSS contract of the stage

The page styles the stage with custom properties, on the stage or on an element above it. It does not style the classes of the stage.

| Property | Meaning | | --- | --- | | --product-scenes-zoom | The zoom of the screen (a number, default 1). The stage makes it smaller where the screen does not fit (fit). | | --product-scenes-radius | The corners of the frame of the screen (default 0). | | --product-scenes-shadow | The shadow of the frame, for example a ring (default none). | | --product-scenes-border | A border above the screen, inside the frame, for example 1px solid white (default none). It does not make the frame wider. | | --product-scenes-pointer-scale | The size of the pointer of the system on the page, in times its real size (a number, default 1.4). The zoom of the screen does not change it. |

The frame has the size of the screen at its zoom, thus the corners, the shadow and the border are at the size of the page.

.hero {
  --product-scenes-zoom: 0.9;
  --product-scenes-radius: 12px;
  --product-scenes-shadow: 0 0 0 1px rgb(0 0 0 / 60%);
}

The scheme of the page

A stage with scheme="page" is dark when the root element of the page has data-product-scenes-scheme="dark", and light without it. Thus a page with a switch of the scheme changes the attribute on <html>, and its scenes follow without a script and without a second render. Set the attribute before the first paint (an inline script in the head), else the scenes show light first.

<html data-product-scenes-scheme="dark">

The stage gets the light and the dark ramp of its hue and tint, and CSS selects one. The page must not set data-color-scheme on an element above the stages: the rules of the cms for a scheme also match an ancestor.

The page sets data-paused on the screen (.ProductScenesScreen) when the screen is not in view: the animations that loop, for example the caret of StructuredTextInput.SlashMenu, stop.

<Stage description="The editor of a page." width={360} height={480} hue={45} scheme="dark" tint={3}>
  <RecordForm title="Spring sale" icon="🏠">
    <RecordForm.Field label="Title" required>
      <RecordForm.StringInput value="Spring sale" large />
    </RecordForm.Field>
  </RecordForm>
</Stage>

Sequences

A scene can play an interaction in a loop: the open gap of a modular content field (ADD_BLOCK_SEQUENCE), a block of a modular content field that the editor drags one place down and drops, with the highlight of the cms after the drop, then the block that went up, which the editor drags one place down too, thus the loop comes back to the first order (DRAG_BLOCK_SEQUENCE, with ModularContentField.Block dragged, dragBack on the next block, and dropLine "top" on the block after it), the slash menu of a structured text field (SLASH_SEQUENCE), a block of a structured text field that the editor selects (BLOCK_SELECT_SEQUENCE, with StructuredTextInput.Block selected), the pointer that comes on a record in the text and shows its tooltip (RECORD_TOOLTIP_SEQUENCE, with StructuredTextInput.InlineRecord hovered), the stage of a record and the tooltip of a stage in its menu (STAGE_MENU_SEQUENCE), the menu of a field that opens, then the submenu of a plugin in it (FIELD_MENU_SEQUENCE, with RecordForm.Field menu and a group with open), the cards of SeoPreviewStack, each in turn at the front (PREVIEW_STACK_SEQUENCE), a click on the Edit button of the SEO field that opens its dialog (SEO_EDIT_SEQUENCE, with RecordForm.SeoInput edit and SeoDialog over), another user that saves a record, leaves, comes back and edits it again, while the pointer of the user that looks cannot click the read-only field until the save (COLLABORATION_SEQUENCE, with RecordForm collaborator, RecordForm.Field saved and pointer, and InfoAside.RevisionHistory saved), the tab Visual, where the edit mode flashes the targets of the page, then a click opens a record and a second click opens a linked record (VISUAL_EDITING_SEQUENCE, with VisualEditorLayout, WebPreview mode="visual", WebPreview.Target, RecordForm.Field highlighted and RecordForm.Above, the fields that the form scrolls past), and a click on Save that reloads the page in the plugin Web Previews (PREVIEW_RELOAD_SEQUENCE, with WebPreview reloaded and RecordForm dirty="until-saved"), the menu Add new field of a model that opens, then the card of a field type under the pointer (ADD_FIELD_SEQUENCE, with AddFieldMenu hovered), the pointer that goes on three field types of that menu in turn, each with its card (HOVER_FIELD_TYPES_SEQUENCE, with a list of three types in AddFieldMenu hovered), and a click on a field of a model that opens its dialog on the tab Settings, then a click on the tab Presentation and a click on the tab Validations (FIELD_SETTINGS_SEQUENCE, with ModelPage clicked and FieldSettings over; RecordEditorLayout sidebarWidth={400} gives the sidebar of the schema its width), the same click on a text field, then a click on the tab Presentation and a click on the button Change of its editor, that opens the menu of the editors (FIELD_EDITOR_SEQUENCE, with FieldSettings type="text"), and the subtitles that the cms generates from the audio of a video: the buttons of the panel, the dialog, then the new track (GENERATE_SUBTITLES_SEQUENCE, with AssetDetail.Tracks sequence, a track with added, and AssetDetail.GenerateSubtitles sequence in AssetDetail dialog), a click on an asset of the media area that opens its dialog (OPEN_ASSET_SEQUENCE, with a MediaArea asset with clicked and AssetDetail over), the same click, then a click on the image of the asset that sets its focal point, with the previews of the crops around the center, then around the point (OPEN_ASSET_FOCAL_POINT_SEQUENCE, with the same props and AssetDetail sequence and focalPoint), and a run of the plugin AI Translations in the aside: the click on Translate all fields, four batches of jobs with a progress bar and a line for the job in progress, the unsaved changes in the toolbar, and the message at the end (AI_TRANSLATIONS_SEQUENCE, with InfoAside.AiTranslations sequence and RecordForm dirty="after-translations"). Give the sequence to the stage:

<Stage description="…" sequence={ADD_BLOCK_SEQUENCE}>

The screen gets data-scene-steps="rest:1600 gap:900 …": the name of each step and its time in ms. The page plays it with a script: it sets data-step on the screen (.ProductScenesScreen) to each name in turn, and loops. The CSS of the part shows the step, with the transitions and the animations of the cms. The screen also gets the time of each step in a custom property (--scene-step-gap: 900ms): an animation that lasts for all its step reads it. Without data-step, the screen shows the last step: the picture at rest. Thus the page shows the picture without JS and with reduced motion, and the script stops the loop when the scene is off screen.

The kit sends no JS: the script is of the page.

Marks over the parts

A scene can put a layer of the website over a part of the cms, for example a ring and a tag that tell the name of a hook of the plugin SDK. The layer covers the box of the part, gets no pointer events, and can go out of the box: give its look in the CSS of the website. The plugin tab of NavigationBar (pluginTabs[].mark) and the publish button of RecordForm (publishMark) take a layer, because the website cannot put content in them. RecordForm.Outlet and RecordForm.PluginInput take content from the website, thus the website puts its layer in that content.

The Rules of a Scene

  • No client: directive. A scene is static HTML. The package sends no JS to the browser, and the parts do not work in the browser.
  • Controlled inputs with constant values. Give each input its value as a prop. Do not keep a state, and do not wait for a user action.
  • No overlays and no portals. Do not open a dropdown, a tooltip, a modal or a popover: there is no JS to place it. Show the closed state. The exceptions are in the parts: StructuredTextInput.SlashMenu and the addAbove prop of ModularContentField.Block (open, at rest, with one option under the pointer and its card if you give hovered), the hovered prop of StructuredTextInput.InlineRecord, the menu of the stage of InfoAside.RecordInfo (open, with the tooltip of the stage under the pointer if you give hovered), the menu of RecordForm.Field (the dropdown of the actions of the field, open; the group with open shows its submenu, for example the actions of a plugin), the hovered prop of RecordForm.VideoInput (the actions of the card on the thumbnail, as under the pointer), AddFieldMenu (the menu Add new field, open, with the card of a field type under the pointer if you give hovered), and the dragged prop of ModularContentField.Block (the picture of a drag, still).
  • Fixed dates and fixed data. Write the dates as text. Do not read the clock and do not fetch, thus two renders give the same picture.
  • One Stage for each scene, with a description. The screen is hidden from assistive technology, thus the description is the only text that a screen reader hears. Write what the picture shows. A page can show many stages.