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

@kwtv/admin-components

v0.17.1

Published

Astro components and React islands for the KWTV website admin panel, built on KWTV tokens.

Readme

@kwtv/admin-components

Admin panel components for the KWTV website, built from the KWTV admin design. The package follows the same split as @kwtv/components: presentational components are Astro; controls that manage interaction are React islands. The admin has its own tuned light and dark colour sets, defined in admin.css.

Install

npm install @kwtv/admin-components @kwtv/tokens

Import both stylesheets once in your app root:

---
import "@kwtv/tokens/tokens.css";
import "@kwtv/admin-components/styles/admin.css";
import AdminButton from "@kwtv/admin-components/astro/AdminButton.astro";
import AdminStatusBadge from "@kwtv/admin-components/astro/AdminStatusBadge.astro";
---

<div class="admin-theme">
  <AdminButton variant="primary" icon="world-upload">
    Publiceren
  </AdminButton>
  <AdminStatusBadge status="draft" />
</div>

Astro components render native controls and semantic markup without a React bundle. Inputs, selects, checkboxes, choices, and section cards use native browser behaviour. The switch is a button with role="switch"; its Astro version ships a small script that toggles it, and renders a hidden input when it has a name. For controls with app state, add Astro's React integration and hydrate the island:

---
import AdminCodeInput from "@kwtv/admin-components/react/AdminCodeInput.tsx";
---

<AdminCodeInput client:load />

Import several React components from @kwtv/admin-components/react when composing a React island. The React entry also includes presentational counterparts so those islands can stay self contained.

A control that is off for a reason takes disabledReason (read out by screen readers) or disabledReasonId (the id of a visible note). It gets aria-disabled and aria-describedby instead of native disabled, so it stays focusable. Plain disabled stays native.

Every field control takes value and onChange, id, name, required, readOnly and the aria-* props, so a form library such as Payload's useField can drive it. Input, Textarea, Select, PrefixInput, Switch, Check and Choice forward a ref to the focusable element. AdminField gives its control the id and wires the hint, reason and error to it. Their ids come from the control id (adminFieldIds(id)), so an error summary can link to #id. Use group for a radio group, a segmented control or a media picker, which a label element cannot name. Pass a function as children for a control from another library, such as a date picker, and spread the props it gets.

Wrap the admin in .admin-theme. With no theme pinned it follows the system setting, like @kwtv/tokens. Set data-theme="dark" or data-theme="light" on <html> to choose one, or use .admin-theme--light and .admin-theme--dark to pin a theme on the element, for example to show both side by side in a gallery. is-hover, is-focus, and is-active classes pin interaction states for visual review.

Put .admin-theme on <body> when the whole page is the admin. The canvas around and behind the page then shows the admin background too: <html> becomes transparent, so the body background fills it. A rule of your own on html still wins.

Links take --link inside the package's own text containers: notes, field hints and the label row, meta lists, dialog bodies, empty states, the sign-in card and rich text. Anchors elsewhere under .admin-theme keep the host's colour. Add .admin-prose to any container of your own whose links should look the same.

Reduced motion stops every animation and transition under .admin-theme, ::before and ::after included. It follows prefers-reduced-motion, and data-motion="off" on the .admin-theme element forces it. .admin-rm does the same for a subtree.

Hosts with a different root font size

Some sizes come from the token scale, which is in rem. A host with a root font size other than 16px, such as Payload's admin, shrinks them and not the px sizes next to them. Set --kw-rem on :root to keep the scale where it was designed:

:root {
  --kw-rem: 16px;
}

It must be on :root. See the @kwtv/tokens README.

Brand

AdminMark is the KW mark, a sage-light tile in both themes. It is decorative unless it has a label. AdminBrand is the mark with the name and a second line, as the sidebar and the sign-in card show it. The mark is a placeholder until the final files are ready. Its SVG lives in src/brand/mark.ts.

Astro components

Icon, Mark, Brand, Button, IconButton, Badge, StatusBadge, Tag, LocaleMarks, Input, Select, PrefixInput, Switch, Checkbox, Check, Choice, ChoiceGroup, Avatar, Kbd, Thumb, Skeleton, Progress, Field, Alert, Note, SitePill, SaveState, Breadcrumbs, EmptyState, Meta, Steps, NavItem, Tooltip, Sidebar, AuthCard, StrengthMeter, PageHeader, Panel, Fields, RowList, Row, SiteStatus, SectionCard, SerpPreview.

React islands

Textarea (counter and meter), CodeInput (one field, so paste and autofill work), Segmented, FilterButton, Toast and Toasts (duration, pause on hover and focus), Tabs, LocaleSwitch, Pager, Search (keyboard shortcut), AccountMenu, TopBar, ListToolbar, DataTable, BulkBar, PublishPanel, SectionRow, InsertPoint, DropLine, Tree, TreeItem, RichText, MediaGrid, MediaTile, MediaField, RecoveryCodes, TotpSetup, PasswordField, Shell, Sidebar, MobileBar, AuthCard, Dialog, Drawer.

Icons are Tabler glyphs rendered as inline SVG with the admin-ic class. They size with font-size. The Astro icon component embeds only the glyphs used by the library; React islands use @tabler/icons-react. Import Hanken Grotesk in the application as on the public site.

Icons in React

The React entry ships no icon names. A bundle holds only the icons its app and the components it renders use. Every icon prop, such as icon on AdminButton, AdminBadge or AdminEmptyState, takes a Tabler component or a name.

A component works as is:

import { IconWorldUpload } from "@tabler/icons-react";

<AdminButton icon={IconWorldUpload}>Publiceren</AdminButton>
<AdminIcon icon={IconWorldUpload} />

A name, such as "world-upload" or "ti-world-upload", has to be registered first. This is the way for names that come from data, such as a Payload collection config. Register them once, in a client module that loads before the admin renders. In Payload that is a module the admin providers import.

"use client";
import { registerAdminIcons } from "@kwtv/admin-components/react";
import { IconHome, IconWorldUpload } from "@tabler/icons-react";

registerAdminIcons({ home: IconHome, "world-upload": IconWorldUpload });

registerAdminIcons adds to one registry for the whole page, so it needs no provider and can run more than once. <AdminIcon name="home" /> and icon="home" then render IconHome.

A name that is not registered renders a circle. In development it also logs one warning per name, which says what to add. Production logs nothing.

node internal/scripts/list-admin-icon-names.mjs <src dir> in this repository scans an app's source for icon names and prints the registerAdminIcons call to paste.

@kwtv/admin-components/react/icons-all registers every name the package knew before 0.7.0, about 140 icons and 49 KB of minified JavaScript. Importing it is enough. The explorer uses it, and it works as a stopgap while an app moves to its own list:

import "@kwtv/admin-components/react/icons-all";

Performance

See the performance rules. An app gets long immutable caching by letting its bundler hash the CSS and font files, and by serving them with public, max-age=31536000, immutable. The App checklist in that page has the details.

Stylesheets

styles/admin.css is every admin rule in one request. Keep importing it if your app uses most of the package. The same rules also ship as area files, in cascade order:

core, shell, layout, forms, sections, content, dialogs, lists, media, wireframes, atoms, molecules, patterns, panels, orders, bars.

Import them as @kwtv/admin-components/styles/admin/<area>.css. Always load core first, then the others in the order above. Each file declares the layer order and keeps its rules in the components layer, so the order only matters between areas that style the same element. The area files are the source. Run yarn admin-css:build in the design system repo after editing one, which rewrites admin.css. yarn admin-css:check fails when it is out of date.

Soft navigation

Internal links render as plain <a href>, so a click loads the whole page. Give the package your router's link component once and every internal link in the package uses it: nav items, breadcrumbs, the account menu, table cells, rows, media tiles, calendar events and link buttons. The package has no router dependency.

Wrap the admin in AdminLinkProvider from a client component. With Next:

"use client";
import Link from "next/link";
import { AdminLinkProvider } from "@kwtv/admin-components/react";

export function AdminLinks({ children }: { children: React.ReactNode }) {
  return <AdminLinkProvider component={Link}>{children}</AdminLinkProvider>;
}

In a Payload app, render AdminLinks in your admin.components.providers entry (or inside your shell glue component), above every component from this package. Run yarn generate:importmap after adding it. A function cannot cross from a server component to a client one, which is why the provider takes the component inside a client file.

External links, mailto:, #hash links, downloads and target="_blank" stay plain anchors. Tabs and the pager are buttons that call onChange, so call router.push there. AdminPager also takes pageHref={(page) => url}: the page buttons then become real links through the provider, and onChange still runs on a plain click. AdminCommandSearch follows item.href with a full load unless you pass onOpenItem, where you can call router.push.

Panel layouts

AdminStack, AdminColumns and AdminActions have React and Astro versions. They use the existing package layout styles, so hosts can arrange panels without local layout CSS.

<AdminStack>
  <AdminPageHeader title="Mijn account" />
  <AdminColumns>
    <AdminStack>{profileAndPasswordPanels}</AdminStack>
    <AdminStack>{securityAndSessionPanels}</AdminStack>
  </AdminColumns>
</AdminStack>

AdminStack separates its children by 24px. AdminColumns uses equal columns with a minimum content width of 360px; narrower hosts stack the children in one column. AdminActions wraps buttons with the package's action spacing. All three accept normal div attributes, including className in React and class in Astro. They add no landmark role. The host supplies headings, labels and behavior.

Shell status lines

AdminTestModeBar and AdminCheckoutPausedLine have React and Astro versions. Both render role="note", use decorative icons and have no close control. The host supplies translated copy. Select test state and settings access on the server. These components do not read payment keys or permissions.

<AdminShell
  sidebar={<AdminSidebar groups={groups} />}
  topBar={
    <AdminTopBar
      user={user}
      siteUrl={siteUrl}
      siteLabel="Bekijk webshop"
      showSiteStatus={canReadSiteStatus}
    />
  }
  mobileBar={
    <AdminMobileBar
      user={user}
      groups={groups}
      siteState={canReadSiteStatus ? siteState : undefined}
    />
  }
  aboveBar={
    isTestMode && (
      <AdminTestModeBar label="Testmodus:">
        betalingen zijn niet echt.
      </AdminTestModeBar>
    )
  }
  belowBar={
    checkoutPaused && (
      <AdminCheckoutPausedLine
        action={
          canManageSettings
            ? { href: settingsHref, label: "Heropenen in Instellingen" }
            : undefined
        }
      >
        Afrekenen staat op pauze sinds {pauseDate}. Klanten kunnen kijken, maar
        niet bestellen.
      </AdminCheckoutPausedLine>
    )
  }
>
  {children}
</AdminShell>

The copy above illustrates the Dutch defaults used by the shop. In an application pass these strings from its translation namespace, and format pauseDate there. AdminTestModeBar requires label and children. AdminCheckoutPausedLine takes children and an optional action with href and label. Both accept className in React and class in Astro. Astro messages use the default slot.

AdminShell.aboveBar appears above whichever bar is active. belowBar appears below it, before main content. On phones the bars and status lines stay together while scrolling. An open menu starts below them. Both slots are optional, so existing shell calls keep their appearance.

AdminTopBar.showSiteStatus defaults to true. Set it to false to omit the status pill from the rendered tree. siteLabel defaults to Bekijk website; pass translated copy for another public site. The site link keeps its external-link icon, target="_blank" and rel="noopener noreferrer". The mobile bar's existing optional siteState remains its status control.

Payload shell integration

Payload 3.90.1 renders admin.components.header before a host div containing its nav toggler and .template-default. The template contains its own .nav and .template-default__wrap, with AppHeader followed by the view. Use AdminPayloadShellHeader in that header mounting point rather than wrapping the view in AdminShell.

Import the opt-in stylesheet once, after the normal package styles:

import "@kwtv/tokens/tokens.css";
import "@kwtv/admin-components/styles/admin.css";
import "@kwtv/admin-components/styles/payload-shell.css";

Keep the theme provider's .admin-theme wrapper. AdminPayloadShellHeader must be its direct child, followed immediately by Payload's host div. This is the DOM produced by Payload's default template when the provider wraps its children. The package adds no Payload or Next runtime dependency.

// The Payload header glue component receives server-filtered data.
return (
  <AdminPayloadShellHeader
    sidebar={<AdminSidebar groups={groups} onSignOut={signOut} />}
    topBar={
      <AdminTopBar
        user={user}
        siteUrl={siteUrl}
        siteLabel={t("kwtv:shell:siteLink")}
        showSiteStatus={canReadSiteStatus}
      />
    }
    mobileBar={
      <AdminMobileBar
        user={user}
        groups={groups}
        siteState={canReadSiteStatus ? siteState : undefined}
        onSignOut={signOut}
      />
    }
    aboveBar={
      isTestMode && (
        <AdminTestModeBar label={t("kwtv:shell:testLabel")}>
          {t("kwtv:shell:testMessage")}
        </AdminTestModeBar>
      )
    }
    belowBar={
      checkoutPaused && (
        <AdminCheckoutPausedLine
          action={
            canManageSettings
              ? { href: settingsHref, label: t("kwtv:shell:reopen") }
              : undefined
          }
        >
          {pauseMessage}
        </AdminCheckoutPausedLine>
      )
    }
    breadcrumbs={<AdminBreadcrumbs items={breadcrumbs} />}
    skipLabel={t("kwtv:shell:skip")}
  />
);

Register that glue component in admin.components.header. Keep default collection list and edit views and the theme provider. The package stylesheet hides Payload's own nav, header and toggler only in this opted-in structure. Other Payload templates, auth views and ordinary AdminShell consumers are unchanged. The application's bridge stylesheet can stay limited to token mappings.

The glue that renders AdminPayloadShellHeader must be a client component, since it manages skip-link focus and observes view changes. A server header can prepare authorized data and pass it to that client glue, as the club's Shell and ShellFrame do.

AdminPayloadShellHeader props:

| Prop | Default | Purpose | | ---------------------- | ------------------------- | ------------------------------------------------------ | | sidebar | Required | Package sidebar with authorized navigation | | topBar, mobileBar | Omitted | Desktop or tablet bar, and phone bar | | aboveBar, belowBar | Omitted | Status slots surrounding the active bar | | breadcrumbs | Omitted | Content below the status lines | | mainId | admin-main | Skip-link target on the real Payload content container | | skipLabel | Naar de inhoud | Translated skip-link label | | contentSelector | .template-default__wrap | Content selector relative to the theme wrapper | | className | Omitted | Additional host class |

The skip link focuses the existing content container with tabIndex=-1. Payload's edit view already emits a main; the header preserves it. List views have no main, so the content container gets role="main". Client navigation updates this choice when the view changes. Unmounting restores the original attributes. It adds no second main around an edit screen.

Stable mounting markers are data-admin-payload-shell="header", "sidebar" and "bars". The package classes are .admin-payload-shell__side, __bars, __top, __mobile and __crumbs. The stylesheet opts in only when the theme wrapper directly contains the header. No separate class on body is needed for layout; retain the host's body theme tagging for portals.

For the club's existing ShellFrame, replace its skip-link, sidebar and bar wrappers with AdminPayloadShellHeader and pass the same children as props. Remove only its old shell layout rules from payload-overrides.css; retain unrelated Payload view and field styling. Keep search dialogs as siblings in the theme provider. After updating the glue component, regenerate Payload's import map as usual. Check this mounting structure when upgrading Payload, since its template classes are internal.

Payload list integration

AdminPayloadList is a React wrapper for a collection's beforeListTable slot. Keep Payload's default list view. Its providers still own access, query, sorting, pagination, selection and authorized data. Import the opt-in stylesheet after the normal admin styles:

import "@kwtv/admin-components/styles/payload-list.css";
import { AdminPayloadList } from "@kwtv/admin-components/react";

return (
  <AdminPayloadList>
    <AdminPageHeader title={title} actions={createAction} />
    <AdminListToolbar
      query={query}
      onQueryChange={setQuery}
      filters={filters}
    />
    <AdminDataTable columns={columns} rows={authorizedRows} caption={title} />
    <AdminPager page={page} pageCount={pageCount} onChange={setPage} />
  </AdminPayloadList>
);

The host maps Payload's list state and callbacks to those package components. The wrapper adds no data requests, providers or main landmark. It accepts standard div attributes, children and className. Its stable marker is data-admin-payload-list; its package class is .admin-payload-list.

Payload 3.90.1 places beforeListTable directly inside .collection-list__wrap. Only a .collection-list with that direct mounting structure opts in. The stylesheet hides its direct .list-header, .list-controls, .collection-list__tables, .no-results and .page-controls siblings. It preserves other slots, nested and unmarked lists, shell navigation and breadcrumbs. Lists inside .drawer or .list-drawer never opt in, even if their slot contains a marker. Check this host structure when upgrading Payload, since its classes are internal.

The /admin-payload-list/ explorer fixture contains marked, nested, unmarked and drawer lists. After building the explorer, run its focused browser check:

yarn test:browser internal/tests/admin-payload-list.spec.ts

Permission groups

AdminPermissionGroup has React and Astro versions. Import its stylesheet with the normal admin styles:

import "@kwtv/admin-components/styles/permissions.css";

The package renders a fieldset, its legend and checked count, then the labelled checkboxes with their permission keys. The host supplies all translated copy. countLabel is a translated template with {selected} and {total} placeholders. The component replaces those placeholders with its own checked and total counts. Permission keys must be unique within each group.

<AdminPermissionGroup
  title={t("permissions.orders")}
  icon={IconReceipt}
  countLabel={t("permissions.count")}
  items={[
    {
      key: "order.view",
      label: t("permissions.viewOrders"),
      checked: true,
      disabledReason: t("permissions.requiredByEdit"),
    },
    {
      key: "order.edit",
      label: t("permissions.editOrders"),
      checked: true,
    },
    {
      key: "order.complete",
      label: t("permissions.completeOrders"),
      checked: false,
      note: t("permissions.openQuestion"),
    },
  ]}
  onChange={(key, checked) => updatePermission(key, checked)}
/>

React takes controlled items and calls onChange(key, checked) only for an allowed checkbox change. The host updates items. A disabledReason keeps the checkbox focusable, applies aria-disabled and prevents pointer and keyboard changes. Its visible note is associated with that checkbox. reasonIcon can replace the default link icon. The host decides which permissions depend on others and enforces those rules on the server.

title and countLabel are required. icon, name, onChange and extra fieldset attributes are optional in React. The component does not accept native fieldset disabled, since that would remove locked controls from keyboard focus. Each item takes key, label, checked, and optional disabledReason, reasonIcon and note. React labels and notes can be nodes. String labels remain the checkbox names while keys, reasons and notes become their descriptions.

Astro accepts the same content fields as strings, plus id, class and name. It uses native checkboxes and updates the translated count after a change. The Astro icon props use package icon names. Add React's onChange when app state must compute dependent permissions.

Catalogue fields

Import @kwtv/admin-components/styles/catalogue.css after admin.css for the catalogue components and reordered tables. The host supplies translated labels, permissions, validation and persistence. None of these components fetch data or change stock themselves.

AdminPrefixInput now accepts an optional suffix and an optional prefix. Both are decoration outside the value. Use suffix={t("characters")} for a maximum length. React and Astro retain the existing input attributes, reasons and descriptions. React retains its input ref. Existing prefix calls work as before. AdminSerpPreview accepts paper and a translated label in React and Astro. paper keeps the Google preview white with its Google text colours in both themes. Its default remains the existing theme behavior.

AdminColourInput is a controlled text input with a decorative swatch. It has React and Astro versions. React accepts the same attributes and input ref as AdminInput, except type, trailingIcon and defaultValue. value is a required string. The swatch displays only three or six digit hex colours. The host validates and stores the text. Wrap it in AdminField to associate the translated label, error and read-only reason, as for an ordinary input.

AdminErrorSummary has React and Astro versions. It takes title, errors and optional id, tabIndex (tabindex in Astro) and class. Each error has an id, fieldId and label. Empty errors render nothing. Links target the encoded field ID. React forwards its ref to the alert and accepts onSelect(error, event). The default is a native fragment link. For a hidden field, prevent that default, open its tab, then focus its input after that tab has rendered. The package owns the heading and list, while the host owns tab and field behavior.

AdminOptionGroup is a React island with a fieldset and an ordered list. Pass title, items and optionally empty. Each item has a stable id, label, optional hex swatch and optional surcharge node. Format the surcharge from integer cents in the host. These optional action props add package controls:

| Props | Behavior | | --------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | addAction: { label, onClick } | Adds the value button | | editLabel(item), onEdit(id) | Adds the edit button to each value | | moveUpLabel(item), moveDownLabel(item), onMove(id, targetIndex) | Adds order buttons and emits a zero-based target | | firstReason, lastReason | Explains the locked boundary button; without a reason it is natively disabled | | readOnlyReason | Shows a note and keeps all supplied actions focusable and locked |

The host applies moves to items; the component keeps no separate order. It also accepts ordinary fieldset attributes except native disabled, which would remove locked actions from keyboard focus.

AdminVariantMatrix is a React island. It takes caption, cornerLabel, rows, columns, cells, absentLabel, optional empty, className and onChange(id, value). Axes have stable id, label and optional swatch. Cells have id, rowId, columnId, value: number | null and a translated input label. Optional cell props are stockLabel, error, readOnly, readOnlyReason and min. Cell IDs become input IDs, so summaries can target them. Input callbacks emit safe integers or null for an empty field. The host decides whether those integers represent stock or cents. Without a callback the cells are read-only. Missing cells show absentLabel. Row and column headers remain associated with every cell, and the row header stays visible while the region scrolls on phones. The region is keyboard focusable.

Reordered tables

AdminDataTable accepts an optional controlled reorder object. Import catalogue.css for its grip placement in desktop rows and phone cards. Pass stable getRowId values and retain the existing columns and rows:

<AdminDataTable
  columns={columns}
  rows={categories}
  getRowId={(row) => row.id}
  reorder={{
    label: t("order"),
    handleLabel: (row) => t("moveCategory", { name: row.name }),
    instructions: t("reorderInstructions"),
    onMove: (id, targetIndex) => updateOrder(id, targetIndex),
    getDisabledReason: (row, index, target) => boundaryReason(index, target),
    announcement: (row, index, total) =>
      t("categoryPosition", { name: row.name, position: index + 1, total }),
  }}
/>

The grip accepts pointer dragging and ArrowUp, ArrowDown, Home and End. Escape cancels a pointer move. The host applies the zero-based target to its controlled rows. A rejected move leaves the table order unchanged. The table announces an accepted move and restores focus to that stable row's grip. onMove can return void, boolean or Promise<void | boolean>. A synchronous callback must update the controlled rows in the same commit. An ignored move clears after that commit. An asynchronous callback returns a promise that settles after its controlled update. Returning false or rejecting cancels the request. Returning void or true accepts it only if the controlled rows have reached the target when the callback settles. Escape, a newer move and unmount cancel pending requests. Later external row updates do not complete cancelled requests. onAnnounce(text) optionally receives the same announcement. Supply getDisabledReason(row, index, targetIndex) for boundaries and blocked moves, and disabledReason(row, index) to lock a whole row while preserving focus. Reasons are read with the grip and announced when a blocked move is tried. Neither callback determines authorization; enforce permission and order rules in the application's server.

Payload edit integration

AdminPayloadEdit is a React UI field at the start of Payload's native main field list. Import @kwtv/admin-components/styles/payload-edit.css after the normal package styles. Keep the default edit form and its providers. Put the package header, locales, tabs and error summary inside the marker; put main panels in the remaining main fields and publish panels in native sidebar fields. The host controls tab visibility with the existing layout components' hidden attribute. No second form or main landmark is added.

The adapter measures its header to span the native main and sidebar columns. It offsets sidebar panels below the header, keeps two columns at tablet widths and stacks them below 641px. It restores its layout variables on unmount. Only a direct marker in that native main field list installs its observer. Nested markers inside panels do not change the enclosing edit layout.

Payload 3.90.1 views/Edit/index.js in @payloadcms/ui mounts the native controls as direct form children, then collection-edit__main-wrapper, collection-edit__main and document-fields. DocumentFields/index.js mounts the marker under document-fields__main, document-fields__edit and render-fields. The main render-fields also has document-fields__fields. Only this exact structure opts in. The stylesheet hides direct native doc-header and doc-controls siblings, keeping their components mounted for autosave. Nested markers, unmarked edits and edits inside .drawer or .document-drawer retain native chrome and layout. Recheck the host structure and cascade when upgrading Payload. The package adds no Payload dependency.

The /admin-catalogue/ fixture covers React catalogue controls, Astro field counterparts, native edit scope and controlled reorder callbacks. After building the explorer, run yarn test:browser internal/tests/admin-catalogue.spec.ts.