Review brief: Media Library¶
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¶
- 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-modalon the dialog,activeon the current tab,media-library-item--disabled), and the pilot uses them, but they don't cover the layout. The pilot therefore addsds-*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 - Media Library declaring its own tokens. The pilot declares
media-card.*,media-panel.*andmedia-tabs.*, each with a suggested mapping to a semantic token. Once a shared vocabulary exists, would Media Library ship amedia_library.tokens.ymllike that itself? → §4 step 1, Q15 - 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?
- Removing theme CSS. The pilot drops the CSS of any library a theme attaches to
media_library/ui,media_library/viewormedia_library/widgetwithlibraries-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 ofmedia_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-actionsandfield/field__item/field-content. css/media-library.css: 467 lines, logical properties only. Colors come only fromvar(--ds-*)(apart fromtransparent); some sizes and timings are still literal (the13remtab column, the1.25remcheckbox,1px–3pxborders,0.15stransitions, which only run underprefers-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-gridpage 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 markeddata-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 | ![]() |
![]() |
| Default Admin | ![]() |
![]() |
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 havepointer-events: none, so a click no longer toggles its checkbox; the item keepscursor: 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-gridlost its grid layout. Its selection form now getsds-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 to6.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¶
- §3 Problem statement
- §4 Declare, fill, render (Media Library is the
example; its sample maps
media-card.bordertocolor.border.primary, while the pilot maps it tocolor.border.defaultand usesprimaryfor the selected border) - Q3, Q15
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).



