Here’s a tight a11y/semantics/perf/security pass for the Energy.gov homepage you shared. It’s all “drop-in” changes you can make in your Drupal templates/components without re-architecting the page.


1) Fix generic link text in the hero carousel

Buttons like “Read more here” are not descriptive for screen readers and hurt SEO.

Change (per slide):

<!-- BEFORE -->
<a class="carousel-link usa-button" href="/articles/...">Read more here</a>

<!-- AFTER -->
<a class="carousel-link usa-button" href="/articles/...">
  Read more: Unleashing American Energy in Alaska
</a>

(Repeat for each slide with the actual headline.)


2) Respect reduced-motion in carousels & decorative animations

Provide a no-motion experience and keep animation from stealing focus.

@media (prefers-reduced-motion: reduce) {
  .splide__track, .splide__list, .splide__slide,
  .splide__progress__bar { animation: none !important; transition: none !important; }
}

Also ensure your “Play/Pause” button reflects state:

// After initializing Splide
toggleBtn.addEventListener('click', () => {
  toggleBtn.setAttribute('aria-pressed', splide.Components.Autoplay.isPaused());
});

3) Make the hero background an actual LCP image

Your LCP is likely the large CSS background image. Browsers can’t prioritize it well.

Markup approach (keeps visual design, improves LCP):

<!-- Inside .carousel-hero -->
<img
  src="/sites/default/files/2025-06/17S1_Alaska_SEC.png"
  alt=""
  fetchpriority="high"
  decoding="async"
  width="1350" height="759"
  class="visually-hidden lcp-helper"
/>

…then keep your existing background CSS. The hidden <img> gives the browser a concrete LCP candidate it can preload/prioritize.

(If you’d rather not add the helper image, add an HTTP/2 server push/preload header for the hero. But the inline <img> is simpler and usually wins.)


4) Ensure all decorative images are truly decorative

Several cards/collections include thumbnails that repeat the title next to them.

If an image is purely decorative:

<img src="/.../supercomputing-homepage-hero_0.jpg" alt="" aria-hidden="true" ...>

Keep meaningful alt where an image conveys content not present in nearby text (your social icons already include hidden text—nice).


5) Mega menu & search toggles: keep ARIA state accurate

You’re using USWDS accordions. Check that the toggles update aria-expanded on open/close, especially the mobile search buttons (megamenu-search-btn[-mobile]). If any are toggled purely by class, add a line to sync ARIA:

const btn = document.getElementById('megamenu-search-btn');
const panel = document.getElementById('search-nav');

btn.addEventListener('click', () => {
  const expanded = btn.getAttribute('aria-expanded') === 'true';
  btn.setAttribute('aria-expanded', String(!expanded));
  panel.hidden = expanded;
});

6) External links & new windows

Your extlink module handles most of this. For manual external links (e.g., social icons), ensure:

<a href="https://www.linkedin.com/company/u-s--department-of-energy"
   target="_blank" rel="noopener noreferrer">
   ...
</a>

You already add screen-reader text (“Link to …”). Good.


7) Clean up analytics/containers in production

You’re loading multiple GA properties and two GTM noscript iframes (one includes gtm_auth and gtm_preview env tokens):

<noscript>
  <iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PCSS9JS&gtm_auth=...&gtm_preview=...">
</iframe>
</noscript>
  • Recommendation: In production builds, remove preview/authed containers and consolidate analytics into a single GTM container whenever possible (it reduces payload and avoids exposing preview tokens).
  • If you must keep multiple GA streams, keep them all in one GTM container and remove duplicate gtag.js loads.

8) Preconnect small perf wins

Since you load multiple analytics domains and GovDelivery:

<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
<link rel="preconnect" href="https://public.govdelivery.com" crossorigin>

(You’re serving Font Awesome locally, so no need for fonts.gstatic.com.)


9) Font Awesome bundle size

You currently ship many FA JS bundles:

all.js
sharp-solid.js
sharp-regular.js
sharp-light.js
sharp-thin.js
sharp-duotone-*.js
fontawesome.js
  • Recommendation: create a single custom FA build including only the families/glyphs in use (or switch to a static SVG sprite). This typically cuts tens/hundreds of KB.

10) Social “Follow Us” icons – focus clarity

You already include visually hidden labels; ensure the icons themselves are not focusable when wrapped in links (they’re not, fine). If you ever add standalone icons, give them aria-hidden="true".


11) OG/Twitter cleanup

  • og:type should be website (not “Homepage”).
  • You already have og:image:alt— 👍
  • Consider adding an Organization JSON-LD (logo + url) for richer entity signals:
<script type="application/ld+json">
{
  "@context":"https://schema.org",
  "@type":"Organization",
  "name":"U.S. Department of Energy",
  "url":"https://www.energy.gov/",
  "logo":"https://www.energy.gov/sites/default/files/2025-03/DOE%20Full%20Seal%20on%20Blue%20background%20-%201312%20x%20825_1.png",
  "sameAs":[
    "https://www.facebook.com/energy",
    "https://twitter.com/energy",
    "https://www.youtube.com/user/USdepartmentofenergy",
    "https://www.instagram.com/energy",
    "https://www.linkedin.com/company/u-s--department-of-energy"
  ]
}
</script>

12) “Trending” links: spacing + hit area

Those are short and close together. Increase inline padding or block spacing to make them easier to tap on mobile (target ~44px touch height).

.sub-hero__links .field__item a { padding: .25rem .5rem; display:inline-block; }
@media (max-width: 640px) {
  .sub-hero__links .field__item { display:block; margin-bottom:.25rem; }
}

13) Newsletter form is solid—add inline error messaging

You already have a <label> and required. Add ARIA invalid and describe on error:

const email = document.getElementById('edit-email');
email.addEventListener('invalid', () => {
  email.setAttribute('aria-invalid', 'true');
  // Optionally set aria-describedby to an inline error <div id="email-error">
});

14) Security header hygiene (server-side)

Not in the HTML, but worth setting at the edge:

  • Content-Security-Policy that includes Dynatrace/GTM/GovDelivery origins explicitly.
  • Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • Permissions-Policy (at least disable camera/mic/geolocation by default).
  • Cross-Origin-Opener-Policy: same-origin

15) Minor head cleanup

  • You have meta name="Generator" twice (Drupal 10 and 8)—keep one, or remove both.
  • Prefer one canonical analytics path (see §7).

Quick wins checklist

  • Replace all “Read more here” with descriptive link text.
  • Add prefers-reduced-motion styles for Splide.
  • Add hidden <img fetchpriority="high"> inside hero to improve LCP.
  • Make repetitive images alt="" aria-hidden="true".
  • Remove preview GTM iframe / consolidate analytics.
  • Preconnect to GTM + GovDelivery.
  • Collapse Font Awesome bundles to a single custom build.
  • Change og:type to website and add Organization JSON-LD.
  • Ensure all menu/search toggles keep aria-expanded accurate.

If you’d like, I can turn these into tiny Twig overrides (carousel slide template, menu toggle behavior, and a head partial) so you can drop them into your theme without touching content.