Skip to content

Best Practices — camoa

Opinionated playbook rules for Drupal projects with Radix/Bootstrap sub-themes. Each rule is atomic and citable.

Rule Guide Summary
Bootstrap before custom CSS Use Bootstrap Before Custom CSS Always check if Bootstrap provides a class, mixin, variable, or utility before writing custom SCSS.
Never replace Bootstrap maps Never Replace Bootstrap Maps Extend Bootstrap maps with map-merge() in a map-overrides partial. Never overwrite Bootstrap's $spacers, $theme-colors, or other maps directly.
Font sizing via RFS Font Sizing — Always Use RFS Never set font-size: directly on headings or body text. Use Bootstrap's @include font-size() mixin which handles responsive scaling via RFS.
Mobile-first breakpoints Mobile-First Breakpoints Use Bootstrap's media-breakpoint-up() for desktop overrides. Define mobile as the base, layer up. Pick the project's stacking breakpoint intentionally.
No direct child selectors with LB No Direct Child Selectors with Layout Builder Layout Builder wraps blocks in variable-depth divs. Never use > in selectors that target Layout Builder content.
LB styles stay at wrapper level LB Styles Must Not Override Component Internals Layout Builder style plugins should apply to the section or block wrapper level. They must not cascade into component-specific elements.
Media view mode → responsive image Media View Mode → Responsive Image Style Never use the default media view mode for content display. Create purpose-specific view modes with responsive image formatters.
Responsive image sizing per context Responsive Image Sizing Per Context Match responsive image styles to actual display size. A card must not serve hero-sized sources.
Sizes-based vs breakpoint-based images Sizes-Based vs Breakpoint-Based Use the sizes attribute when image width depends on layout. Use breakpoint-specific mappings when art-direction differs per breakpoint.
No hardcoded content in templates No Hardcoded Content in Templates All user-facing text must be CMS-editable. Section headings come from block labels or view titles — never from Twig.
Config over code Config Over Code Prefer config-only solutions over custom PHP. When unavoidable, use theme preprocess hooks — not controllers or services.
Field descriptions via config Field Descriptions via Base Field Overrides To add help text to base fields, use core.base_field_override config — not hook_form_alter().
BEF + AJAX over JS toggles BEF + AJAX Over JS Toggles For filtered-view switchers, use Better Exposed Filters with AJAX — not client-side JS that hides pre-rendered content.
Block → SDC via block template Block → SDC via Block Template When a block needs to render as an SDC, create a block template that includes the SDC. Don't hardcode SDCs in page templates.
SDC JS scope SDC Libraries SDC JS that needs global scope must go in theme-level library overrides, not in the SDC's component JS file.
Window width at click time Window Width at Click Time Check window.innerWidth at click time, not at Drupal behavior attach time — once() captures a stale snapshot.
Per-environment module exclusion Per-Environment Module Exclusion Use $settings['config_exclude_modules'] for modules with environment-specific config (GTM, profiling).
Metatag per bundle Metatag Per Bundle Configure metatag defaults per content type, not globally — each bundle uses different field tokens.
Composer module removal order Composer Module Removal Order Never remove a module from composer.json before all environments have uninstalled it via drush config:import.
Hiding features for launch Hiding Features for Launch When temporarily removing a feature, preserve all code — use templates or #access = FALSE and document the re-enable path.
Prefer Drush commands over custom PHP Prefer Drush Commands Over Custom PHP Check whether a Drush command exists before reaching for php:eval or php:script. Built-in commands are tested, idempotent, flag-aware, and self-documenting; php:eval bypasses all of that and accumulates as brittle copy-paste debt.
Avoid unnecessary custom modules Avoid Unnecessary Custom Modules Reuse = CSS + Drupal config (zero new code). Extend = contrib modules, Twig template overrides, preprocess hooks, hook implementations (use the framework's extension points). Create = custom module only when both fail. Contrib is shared infrastructure; your template is yours to patch forever.