RSSAmplifier

Blog by Steve Frenzel · May 5, 2026

Quality assurance, accessibility and Astro: Part 2

0
Sign in to vote or save

Steve Frenzel · Steve Frenzel

Table of contents

In this series, I’ll be exploring the topic of quality assurance, as it’s something that’s interested me for a while and is an aspect of my work as a frontend developer that I’d like to understand better. Here you can read the first part of this series! or skip straight to the third part. You can find the branch for this blog post on Codeberg.

Spring cleaning

It’s May in Germany, the days are getting longer again, plants are starting to bloom, birds are beginning to sing their songs, and the sun is already beating down with great intensity. It’s time for spring cleaning and for me to tidy up the project as much as possible so I can work on it and write (or delete) tests for it! You can find the repository on Codeberg.

In the first part of this series, I migrated it from GitHub to Codeberg and converted it into an Astro project so I can work with it more effectively and take advantage of various features. First and foremost, the component-based structure of the project. This allows me to isolate individual components, along with their styles and logic, and reuse them wherever I want.

Here, I can broadly distinguish between global and individual components. My goal is to make the project significantly more structured, and in the next step, I’ll address the patterns that are somewhat suboptimal from a user perspective and in terms of accessibility. The (cleaning) gloves are on! 🧼

Global changes

Layout

One feature that saves a lot of boilerplate code in Astro is placing content within a layout layer, which styles the content according to the rules defined in that layer. In this case, I use reset.css and global.css for the relevant styles, and import the Navigation.astro component, which must also be available everywhere.

---
import "@styles/reset.css";
import "@styles/global.css";
import Navigation from "@components/Navigation.astro";

interface Props {
  showAuth?: boolean;
  showBackToCart?: boolean;
  showCart?: boolean;
  showContinue?: boolean;
  showSearch?: boolean;
  title: string;
}

const { title, showAuth, showBackToCart, showCart, showContinue, showSearch } =
  Astro.props;
---

Depending on the page, different elements are (or aren’t) displayed in the navigation, which is why there are so many props. In a production environment, I’d look for a more elegant solution, but for this fictional showcase project, it’s sufficient.

<!doctype html>
<html lang="en">
  <head>
    {# Other code #}
    <title>{title}</title>
    <script type="module" src="/src/utils/app.ts" is:inline></script>
  </head>
  <body>
    <Navigation
      showAuth="{showAuth}"
      showBackToCart="{showBackToCart}"
      showCart="{showCart}"
      showContinue="{showContinue}"
      showSearch="{showSearch}"
    />
    <slot />
  </body>
</html>

The <head> tag references app.ts, since, as mentioned in the first part, it represents the heart of this website - containing its core logic - and must be available throughout the site. The individual content of the page is then displayed using this layout within the <slot/> element.

Global CSS file

Here, I was able to remove hundreds of lines of code by moving styles for Navigation.astro, Hero.astro, Filters.astro, Products.astro, checkout.astro, and others into their respective components. Very satisfying!

Specific pages

I was also able to remove a lot of code from the individual pages by replacing repetitive patterns with the appropriate components. Take index.astro as an example, which was reduced from 80 lines to 19 lines of code:

---
import Layout from "@layouts/Layout.astro";
import Hero from "@components/Hero.astro";
import Filters from "@components/Filters.astro";
import Products from "@components/Products.astro";
import Footer from "@components/Footer.astro";
import Toast from "@components/Toast.astro";
---

<Layout title="TechMart - Your Tech Essentials Store">
  <main>
    <Hero />
    <Filters />
    <Products />
  </main>
  <Footer />
  <Toast />
</Layout>

If there were auto-imports like in Nuxt, the components could be even more compact.

Individual changes

Single-purpose components

There are also many small components that are used repeatedly here and there, such as ErrorMessage.astro, Footer.astro, Hero.astro, Input.astro, Navigation.astro, SubmitButton.astro, or Toast.astro.

The input element appears again and again, usually with the same parameters, sometimes with different ones. For this reason, this component accepts a wide variety of props, and depending on whether it is a <input> or <select> element, the corresponding one is displayed:

---
interface Props {
  id: string;
  label: string;
  maxlength?: string;
  minlength?: string;
  options?: Array<{ value: string; label: string }>;
  pattern?: string;
  placeholder?: string;
  required?: boolean;
  type?: "text" | "password" | "email" | "tel" | "submit" | "button";
  variant?: "input" | "select";
}

const {
  id,
  label,
  maxlength,
  minlength,
  pattern,
  placeholder,
  required = true,
  type = "text",
  variant = "input",
  options = [],
} = Astro.props;
---

<div class="form-group">
  <label for={id}>{label}</label>
  {
    variant === "select" ? (
      <select id={id} name={id} required={required}>
        {options.map((item) => (
          <option value={item.value}>{item.label}</option>
        ))}
      </select>
    ) : (
      <input
        id={id}
        name={id}
        type={type}
        maxlength={maxlength}
        minlength={minlength}
        pattern={pattern}
        placeholder={placeholder}
        required={required}
      />
    )
  }
</div>

Checkout components

The components OrderConfirmation.astro, OrderSummary.astro, PaymentInformation.astro, and ShippingInformation.astro are only used in the checkout process, which is why it made sense to me to place them under /src/components/checkout/. This structure also allowed me to keep the individual components compact and easy to maintain.

Accessibility issues

Overall, the project is in ok shape in terms of accessibility, partly because I’ve already fixed issues like the missing accessible name on the <section> element. After running a check with Axe DevTools, I identified problems with color contrast and heading levels. There’s also a so called “Toast” element used in this project.

As Adrian Roselli points out in his article Defining ‘Toast’ Messages, there are many things to consider when implementing this pattern. In the next step, I’ll take a deep dive into this one to make it as accessible as I can. Or I’ll just ditch it and come up with a better, under-engineered solution.

There’s also OrderConfirmation.astro, which is a custom-built modal element. This element is ideally suited to be converted into a native <dialog> element, which is exactly what I’ll do. In addition, native form validation is used, which is acceptable as a bare minimum but also poses various accessibility issues; see Avoid Default Field Validation, another article by Adrian.

This step will also involve testing with the keyboard and with different screen readers so that I can be sure everything works as I expect.

Coming up

I’ve already accomplished a lot, but I still have a long way to go! Before I can get to what I’m actually here to do (write tests), I’m going to focus on making the website accessible. This will likely mean that many of the tests will need to be rewritten or perhaps even deleted, but for now, my priority is ensuring that the website is fundamentally usable.

Read the original on stevefrenzel.dev

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.