Skip to content

Review brief: Media Library

Author: Shibin Das (d34dman)Last updated

In review

Reviewers: seanB

Try it yourself

Everything in this brief runs on the demo site: Drupal 11.5.0-beta1 with the module, its adapters, the Media Library pilot and demo content. You need DDEV 1.25 or newer:

git clone https://github.com/d34dman/design_system_api_demo.git
cd design_system_api_demo
ddev start && ddev install-demo   # prints a one-time login link

Why this reaches your area

Media Library is the pilot: the first module styled only through the contract. It renders on admin routes and, through the media field widget, inside frontend themes, which is exactly the case the project is about. Today every admin theme ships its own copy of Media Library CSS, and a frontend theme usually ships none. The pilot replaces those copies with one stylesheet whose colors, spacing, radii, fonts and shadows come from contract tokens. To get there it hooks into Media Library in ways you may not want to support.

Questions for you

  1. Stable classes in core's templates. Core's Media Library templates carry only js-* hooks. The BEM classes themes style come from the themes' own template overrides and preprocess hooks. Core does emit a few other classes from code (media-library-widget-modal on the dialog, active on the current tab, media-library-item--disabled), and the pilot uses them, but they don't cover the layout. The pilot therefore adds ds-* classes in preprocess and form-alter hooks. Would Media Library accept stable styling classes (or SDC components) in its own templates, so nobody needs to add them from outside? → Q3
  2. Media Library declaring its own tokens. The pilot declares media-card.*, media-panel.* and media-tabs.*, each with a suggested mapping to a semantic token. Once a shared vocabulary exists, would Media Library ship a media_library.tokens.yml like that itself? → §4 step 1, Q15
  3. Fragile spots. The list below shows the classes the pilot adds and the core classes its CSS relies on. Which of them would you expect to change, so we know what to watch?
  4. Removing theme CSS. The pilot drops the CSS of any library a theme attaches to media_library/ui, media_library/view or media_library/widget with libraries-extend, and keeps that library's JavaScript and dependencies. Does any theme rely on that CSS for behaviour, not just looks?

What we built

Submodule design_system_api_media_library:

  • hook_library_info_alter(): adds the pilot's stylesheet as a dependency of media_library/{ui,view,widget}, and strips the CSS of whatever theme libraries extend them. No theme is named.
  • Classes added by hooks:
Hook Class
preprocess_media_library_wrapper ds-media-library
preprocess_links__media_library_menu ds-media-tabs
preprocess_media (view mode media_library, runs last) ds-media-card, ds-media-card__preview, ds-media-card__meta
preprocess_media_library_item ds-media-item; ds-media-item__remove on the remove button
preprocess_views_view (view media_library) ds-media-view
preprocess_views_view_unformatted (view media_library) ds-media-grid__item on each row, on every display of the view, including the /admin/content/media-grid page
form_alter (selection forms of every display, add and filter forms) ds-media-select-form, ds-media-add-form, ds-media-filters
field_widget_complete_media_library_widget_form_alter ds-media-widget, ds-media-widget__selection, ds-media-widget__weights
  • Core and theme classes the CSS also relies on: the jQuery UI dialog classes, media-library-widget-modal, js-media-library-selected-count, views-display-link/is-active, media-library-item--disabled, active, data-media-library-added-delta, view-header, form-item, description/ form-item__description, form-actions and field/field__item/field-content.
  • css/media-library.css: 467 lines, logical properties only. Colors come only from var(--ds-*) (apart from transparent); some sizes and timings are still literal (the 13rem tab column, the 1.25rem checkbox, 1px–3px borders, 0.15s transitions, which only run under prefers-reduced-motion: no-preference, line heights). It takes the place of about 1,700 lines of Media Library CSS (813 in Default Admin, 911 in Claro), but mainly for the dialog and the field widget: the /admin/content/media-grid page gets the grid, the table display gets only a thumbnail size, and the upload progress styles have no pilot CSS. Most rules that set a text color also set the background under it, so that a page marked data-ds-mode="dark" on a theme without dark mode stays readable. Some set only the text color and rely on an ancestor's background, which a theme can style, so they can lose contrast there (.ds-media-library, .views-display-link, .ds-media-card__meta, the selected count).
  • design_system_api_media_library.tokens.yml: the component tokens.
  • PilotSelectionTest (FunctionalJavascript): the selection limit, the focus ring on a selected card, the media grid page layout and the Table display thumbnails.

See it

Pilot off Pilot on
Olivero Olivero, pilot off Olivero, pilot on
Default Admin Default Admin, pilot off Default Admin, pilot on

In the demo: edit an article and open the Media Library from the image field. Turn the pilot off with ddev drush pmu design_system_api_media_library, and back on with ddev drush en design_system_api_media_library.

Known issues

Found in our own check of the code. Items marked Fixed name the commit that fixed them. We track the open ones ourselves; if one matters for your review, say so in this brief's issue.

  • Fixed (b8ffdc0): Items past the selection limit could be selected. A disabled item's parts now have pointer-events: none, so a click no longer toggles its checkbox; the item keeps cursor: not-allowed.
  • Fixed (b8ffdc0): The focus ring was hidden on a selected card. The selected styles are wrapped in :where(), and the focus rule now matches .ds-media-grid__item:has(:focus-visible), so it shows for keyboard focus only.
  • Fixed (3e88937): /admin/content/media-grid lost its grid layout. Its selection form now gets ds-media-select-form, so the pilot's grid rule matches.
  • Partly fixed (3e88937): The table display (widget_table) has no pilot CSS, and the theme's table rules are stripped. Its thumbnails are now kept to 6.25rem; the rest of the table is still unstyled.
  • Upload progress styles are lost: the theme's AJAX throbber and processing-status overlay inside items.
  • Partly fixed (a424851): The stripping is global. A theme library loses its CSS only when the theme uses it for the Media Library alone; the only off switch is still uninstalling the module.
  • Partly fixed (b8ffdc0, 3e88937): The pilot module had no tests. It now has FunctionalJavascript tests for the selection limit, the focus ring on a selected card, the media grid page and the Table thumbnails; library alter, remove and reorder are still untested.
  • Core side finding: Default Admin attaches default_admin/media_library.theme (FormHooks.php), which it doesn't define; the name is left over from Claro. Core issue: #3628096.

Read only these sections

What we heard

seanB (comment #3 on the issue, 2026-10-12, plus a Slack exchange). He answered the problem behind the questions rather than the questions one by one:

  • The hard part is theme CSS leaking in. Media Library is deliberately not an iframe, so its styling has to be kept apart from the page some other way. Tokens don't do that: they decide which values the pilot uses, not which rules reach it. The pilot only wins where it sets a property itself.
  • A reset boundary works today. On a client site he put a generic class (admin-section) on the admin UI, applied a full CSS reset to it, and scoped all admin CSS under it. That holds as long as the theme uses BEM (or similar), avoids nesting and keeps its styles component-specific.
  • Tokens are the other half. If core and themes share well-named CSS variables, each design system ("frontend", "backend") can define its own, and components inherit them from the nearest container marked by a class or data attribute.
  • Long-standing core issue: #2195695 "Admin UIs on the front-end are difficult to theme" describes this case.
  • Next step he suggests: a small proof-of-concept page with both design systems, nesting components every way (backend in frontend, the reverse, several levels), to find where isolation breaks before designing the full solution.

What changes: leakage becomes its own problem in the concept, next to the vocabulary (Q17). A reset boundary at a module's root stops the leak, and tokens make what still gets through land on the right values. It also raises a question we had no answer for: more than one design system on the same page (the site next to the editor UI, or Canvas chrome around a page preview), where a region could declare which system it belongs to and Drupal generates the boundary. Measured on real pages of the demo (this widget and dialog, Navigation, Layout Builder), a reset fence still leaked on every page, and CSS that Drupal scopes per design system almost never did. Once the theme is out, though, the dialog shows browser-default form controls: the pilot needs styling for those too, not only tokens. Next step: a call with Sean.

Questions 1–4 are still open.


Edit note (2026-10-03): Corrected the stylesheet's line count (443), the "tokens only" claim (some sizes and timings are literal), the text/background pairing claim, the list of classes the pilot adds and relies on, what the stylesheet replaces (the ~1,700 lines, but only for the dialog and field widget), core's non-js-* classes, and the §4 sample mapping; added Known issues. All found in a pre-review check of the code.

Edit note (2026-10-03, later): Updated after b8ffdc0 (selection limit, focus ring on selected cards, reduced motion, pilot tests) and 3e88937 (media grid page layout, Table thumbnails): marked those Known issues fixed or partly fixed, and corrected the stylesheet's line count (467), the list of what it covers, and the selection-form hook. The core issue for the side finding is now linked (#3628096). Open known issues are now tracked by the maintainers instead of as separate issues.

Edit note (2026-10-13): Added seanB's review under What we heard and listed him as a reviewer.

Edit note (2026-10-14): Linked concept Q17 and the nesting tests (first simulated, then on real pages) from What we heard.

Edit note (2026-10-18): Marked the global stripping partly fixed (a424851); the demo site now runs Drupal 11.5.0-beta1 (the module supports ^11.5 || ^12).