Drupalwoo ☉

Creating a Drupal Custom Theme with CSS Grid

A custom Drupal theme gives a site a visual system that matches its content model, brand, and publishing workflow. Using CSS Grid for the page structure makes it easier to create flexible layouts without relying on excessive wrapper elements, framework classes, or brittle positioning rules.

This approach suits Drupal sites ranging from local business websites in Brisbane to editorial platforms serving readers across Sydney, Melbourne, Perth, and regional Australia. Grid can handle wide desktop screens, narrow mobile devices, sidebar regions, card layouts, and long-form content while keeping the markup comparatively clear.

The examples below use a modern Drupal theme structure, Twig templates, a theme library, and a small amount of CSS. The same principles apply to Drupal 10 and Drupal 11, although starter themes and generated theme files may vary slightly between releases.

Approach Strengths Limitations Suitable Use
Custom CSS Grid theme Full control, lightweight output, clear layout rules Requires CSS and Twig knowledge Branded websites and tailored content platforms
Olivero subtheme Strong accessibility foundation and Drupal conventions Some inherited styles can be difficult to override Sites needing a quick accessible starting point
Bootstrap-based theme Familiar responsive utilities and components Extra CSS, classes, and dependencies Teams already experienced with Bootstrap
Starterkit-generated theme Modern Drupal workflow and clean ownership Requires initial configuration New projects with long-term maintenance plans

Prepare The Drupal Theme Structure

Before writing layout CSS, create a theme directory inside web/themes/custom, assuming a Composer-based Drupal installation. A practical name such as harbour_grid keeps the custom theme separate from contributed projects and makes its purpose clear.

The minimum theme definition is an .info.yml file:

name: Harbour Grid
type: theme
description: 'A custom CSS Grid theme for Drupal.'
core_version_requirement: ^10 || ^11
base theme: false

libraries:
  - harbour_grid/global

regions:
  header: Header
  primary_menu: Primary menu
  highlighted: Highlighted
  content: Content
  sidebar: Sidebar
  footer: Footer

The base theme: false line creates a standalone theme. For many production projects, a subtheme based on Olivero can reduce accessibility and reset work, but a standalone theme provides a clearer learning path and tighter control over markup. Use the Drupal starterkit command where appropriate so the generated theme has the expected metadata for the installed core version.

Add a harbour_grid.libraries.yml file:

global:
  css:
    theme:
      css/style.css: {}
  js:
    js/theme.js: {}
  dependencies:
    - core/drupal
    - core/once

Create the referenced css and js directories, then clear Drupal caches after changing YAML files. A missing library declaration or indentation error can prevent styles from loading, so browser developer tools and Drupal’s status reports are useful during early setup.

Build The Page Shell With Grid

Drupal’s page.html.twig template controls the main page shell. Print regions in semantic containers and keep the layout logic in CSS rather than adding presentation classes to every element.

<div class="site-shell">
  <header class="site-header">
    {{ page.header }}
  </header>

  <nav class="site-navigation" aria-label="Primary navigation">
    {{ page.primary_menu }}
  </nav>

  {% if page.highlighted %}
    <div class="site-highlighted">
      {{ page.highlighted }}
    </div>
  {% endif %}

  <main class="site-main" id="main-content">
    <div class="layout-container">
      <section class="content-column">
        {{ page.content }}
      </section>

      {% if page.sidebar %}
        <aside class="sidebar-column" aria-label="Sidebar">
          {{ page.sidebar }}
        </aside>
      {% endif %}
    </div>
  </main>

  <footer class="site-footer">
    {{ page.footer }}
  </footer>
</div>

The page variable contains renderable Drupal regions. Check the available region names in the theme’s .info.yml file because a typo can result in content silently appearing in the wrong place. Use page.content for the main output and avoid printing raw entity values when Drupal’s render arrays already provide field formatting, caching metadata, and access handling.

A basic CSS Grid shell could look like this:

.layout-container {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 22rem);
  gap: clamp(2rem, 5vw, 5rem);
  max-width: 76rem;
  margin-inline: auto;
  padding-inline: 1.25rem;
}

.content-column {
  min-width: 0;
}

@media (max-width: 48rem) {
  .layout-container {
    grid-template-columns: 1fr;
  }
}

The minmax(0, 1fr) value prevents long URLs, wide images, and embedded media from forcing horizontal overflow. clamp() provides fluid spacing between phone and desktop widths, which is useful for Australian audiences accessing sites over mobile networks or variable NBN connections.

Define Responsive Content Patterns

A page shell is only the foundation. Most Drupal websites also need grid layouts for teasers, product cards, news listings, related content, and media galleries. A reusable pattern can use automatic column fitting:

.view-card-grid,
.paragraph--type--card-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1.5rem;
}

.card {
  display: grid;
  align-content: start;
  gap: 0.75rem;
  padding: 1.25rem;
  border: 1px solid #d6dce2;
  border-radius: 0.35rem;
  background: #fff;
}

This allows the browser to select an appropriate number of columns without a separate breakpoint for every screen size. It works particularly well for Drupal Views output, where the number of results may change according to exposed filters, taxonomy terms, or editorial decisions.

Use a separate modifier when a component needs a deliberate layout:

.media-feature {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(14rem, 0.8fr);
  gap: 2rem;
  align-items: center;
}

@media (max-width: 42rem) {
  .media-feature {
    grid-template-columns: 1fr;
  }
}

Do not use CSS Grid to disguise poor content structure. Editors should still be able to identify headings, summaries, images, captions, and calls to action in a meaningful order. The visual order should generally follow the DOM order, especially for keyboard users and people using screen readers.

For Australian sites, test cards with long place names such as “Wollongong” or “Wagga Wagga”, varied organisation names, and prices that include GST wording where required. A design that works only with short sample text will often fail when real content arrives.

Connect Twig Templates And Theme Settings

Drupal’s template suggestions make it possible to target specific content types, fields, views, and display modes. For example, a node template such as node--article.html.twig can provide a focused structure for editorial content:

<article{{ attributes.addClass('article-card') }}>
  {% if content.field_image %}
    <div class="article-card__image">
      {{ content.field_image }}
    </div>
  {% endif %}

  <div class="article-card__body">
    {{ title_prefix }}
    <h2{{ title_attributes }}>
      <a href="{{ url }}">{{ label }}</a>
    </h2>
    {{ title_suffix }}

    {{ content|without('field_image') }}
  </div>
</article>

Use without() carefully and confirm that the removed field is rendered elsewhere. Drupal field output can include wrappers, language information, cache metadata, and formatter-specific markup. Inspect the rendered HTML before creating selectors, because a field formatter or contributed module may add classes that affect your CSS Grid rules.

Global visual choices can be stored as theme settings rather than hard-coded values. A simple config/install or theme settings implementation can support options such as a logo, maximum content width, or colour scheme. Keep design tokens in CSS custom properties:

:root {
  --color-ink: #20252b;
  --color-brand: #005a70;
  --color-surface: #f4f7f8;
  --content-width: 76rem;
  --space-unit: 0.5rem;
}

body {
  color: var(--color-ink);
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}

This makes later branding changes safer. It also helps when a site needs a restrained palette for a council, health service, education provider, or Australian not-for-profit organisation.

Make The Theme Accessible And Maintainable

CSS Grid does not automatically make a layout accessible. Use real headings, landmarks, labels, and buttons in Twig templates. Provide meaningful alternative text through Drupal’s media and image fields, rather than hiding a decorative image’s missing description with CSS.

Keyboard focus must remain visible:

:focus-visible {
  outline: 3px solid #f5b700;
  outline-offset: 3px;
}

a {
  color: var(--color-brand);
}

a:hover {
  text-decoration-thickness: 0.15em;
}

Test colour contrast, zoom to 200 percent, and navigate without a mouse. Sites serving Australian government departments or public services should take the Australian Government Digital Service Standard and WCAG expectations seriously. The Privacy Act 1988 also matters when forms, analytics, newsletters, or account features collect personal information; the theme should present privacy notices and consent controls clearly rather than burying them in a visually weak footer.

Keep CSS component-based. Prefer selectors such as .site-header, .card, and .article-card__body over deeply nested selectors tied to a particular Views markup structure. Drupal markup can change when a formatter, module, or display mode changes, and low-specificity styles are easier to override.

Practical Theme Checks

A small theme.js file should be reserved for genuine interaction, such as a mobile menu or disclosure control. Avoid adding JavaScript for layout tasks that CSS Grid can handle. This reduces maintenance and improves performance for visitors on mobile connections in regional Queensland, Western Australia, or remote communities.

Test, Optimise, And Deploy The Theme

Use Drupal’s cache rebuild command during development:

vendor/bin/drush cr

For front-end checks, inspect the page at narrow, medium, and wide viewport sizes. Test a real content listing, an article with embedded media, a search result page, a form, a 404 page, and a page with no sidebar. Browser tools can reveal overflowing elements, unexpected grid tracks, unloaded assets, and invalid nesting.

Deployment Checks For Australian Sites

Optimise images through Drupal image styles rather than serving original camera files. Set width and height attributes where possible to reduce layout shift, and load only the fonts and scripts the design needs. A lightweight custom theme can improve Core Web Vitals without requiring a large front-end framework.

Before deployment, place the theme in version control and export configuration separately from code. Review YAML changes, Twig debugging settings, permissions, and error reporting before moving to production. A staging environment should include realistic content, because a CSS Grid layout can behave differently when editorial fields contain long legal notices, large tables, or translated text.

A well-structured custom Drupal theme separates content management from presentation. Drupal handles regions, entities, fields, Views, permissions, and caching, while CSS Grid supplies a flexible visual system that adapts to the content. With semantic Twig templates, responsive rules, accessibility checks, and disciplined deployment practices, the result is easier to maintain as an Australian website grows.