2026 Web Development GitHub ↗

Overview

The Pharmacy Website is a professional multi-page site for a retail pharmacy, built with accessibility as a primary design constraint rather than an afterthought. The site covers: a landing page with a services summary and pharmacy hours, a products page with categorised OTC medications, vitamins, and health devices, a services page detailing prescription handling, consultation, and delivery, and a contact/location page with an embedded map and enquiry form. The colour palette was engineered to meet WCAG AA contrast ratios (minimum 4.5:1 for normal text), and every interactive element is fully keyboard-navigable. Product categories are marked up with structured schema.org-compatible data attributes to support search engine rich results. JavaScript provides a real-time product search/filter and mobile navigation, while the HTML5 validation layer covers the contact form before any script runs.

Architecture

graph TD subgraph Pages["Pages"] A["index.html\nHero · Services Summary · Hours"] B["products.html\nCategorised Product Listings + Search"] C["services.html\nPrescriptions · Consultations · Delivery"] D["contact.html\nMap Embed · Enquiry Form"] end subgraph Styles["Styles"] E["style.css\nWCAG-compliant Palette · Components · Grid"] F["Responsive Breakpoints\n480px · 768px · 1024px"] end subgraph JS["JavaScript"] G["main.js\nProduct Search/Filter · Mobile Menu · Form Validation"] end A --> E B --> E C --> E D --> E E --> F B --> G D --> G style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style C fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style D fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#181818,stroke:#1e1e1e,color:#888

Tech Stack

  • HTML5 — Semantic landmark elements throughout; aria-label, aria-labelledby, role attributes for screen-reader accessibility; tabindex management for keyboard navigation
  • CSS3 — WCAG AA-compliant colour palette; CSS Grid for product category sections; Flexbox for navigation and service cards; CSS custom properties for consistent token usage
  • Responsive Design — Mobile-first base styles; breakpoints at 480 px, 768 px, and 1024 px; tested with screen readers and keyboard-only navigation at all sizes
  • Vanilla JavaScript — Live product search (filtering cards as the user types), category filter tabs, mobile menu toggle, contact form validation with accessible error announcements
  • Google Maps Embed — Location page uses a responsive iframe embed for the pharmacy map; width and height set in CSS to maintain aspect ratio across viewports

Build Process

1
Accessibility-First Planning

Before any visual design work, the landmark and heading structure was defined on paper: one <main> per page, named <section> elements with aria-labelledby pointing to their <h2>, and ARIA roles where native elements are insufficient. Keyboard navigation paths were planned to ensure logical tab order throughout.

2
WCAG-Compliant Colour Palette

The medical white/blue palette was tested against WCAG 2.1 AA requirements (4.5:1 ratio for normal text, 3:1 for large text and UI components) using the WebAIM Contrast Checker. The initial blue failed at #3b82f6 on white; it was darkened to #1d5fa6 to achieve a 5.1:1 ratio.

:root {
  --color-primary:  #1d5fa6;  /* 5.1:1 on white — WCAG AA pass */
  --color-bg:       #ffffff;
  --color-surface:  #f0f4f9;
  --color-text:     #1a2332;  /* 14.7:1 on white */
  --color-muted:    #5a6474;  /* 4.6:1 on white — AA pass */
  --color-success:  #1a6e38;  /* 5.8:1 on white */
  --color-error:    #b91c1c;  /* 6.0:1 on white */
}
3
Product Categorisation Structure

Products are organised into three categories — OTC Medications, Vitamins & Supplements, and Health Devices. Each product card has a data-category attribute and a data-name attribute that the live search queries against. The HTML structure follows the same <article>-based card pattern as other projects, with an additional itemscope/itemprop layer for schema.org/Product structured data.

4
Live Product Search with JavaScript

A search input on the products page filters cards in real time as the user types. The query is compared case-insensitively against the data-name attribute. An accessible live region announces the result count to screen readers via aria-live="polite".

const searchInput = document.getElementById('product-search');
const cards = document.querySelectorAll('.product-card');
const liveRegion = document.getElementById('search-status');

searchInput.addEventListener('input', () => {
  const query = searchInput.value.toLowerCase().trim();
  let visible = 0;
  cards.forEach(card => {
    const match = card.dataset.name.toLowerCase().includes(query);
    card.style.display = match ? '' : 'none';
    if (match) visible++;
  });
  liveRegion.textContent = `${visible} product${visible !== 1 ? 's' : ''} shown`;
});
5
Map Embed & Contact Form

The location page uses a responsive Google Maps embed wrapped in a container that maintains a 16:9 aspect ratio via aspect-ratio: 16 / 9 on the wrapper and width: 100%; height: 100% on the <iframe>. The contact form validates name, email, and message fields before submission; error messages use role="alert" so screen readers announce them immediately.

6
Mobile-First Responsive Testing

Tested the complete site with keyboard-only navigation, VoiceOver on macOS, and NVDA on Windows at each breakpoint. Verified tab order, focus visible indicators (minimum 2px outline), and screen reader announcement of dynamic content (search results, form errors). Cross-browser tested in Chrome, Firefox, Safari, and Edge.

Patient / Customer Journey Flow

flowchart LR A["Patient / Customer\nArrives on Site"] --> B{"What do they need?"} B -->|"Find a product"| C["products.html\nSearch or Browse by Category"] B -->|"Learn about services"| D["services.html\nPrescriptions · Consultations"] B -->|"Get directions"| E["contact.html\nMap + Hours"] C --> F["Filters / Searches\nJS Updates Result Count via aria-live"] F --> G["Finds Product\nViews Details"] D --> H["Reads Service Info\nScrolls Landmark Sections"] E --> I["Views Map Embed\nChecks Opening Hours"] E --> J["Submits Enquiry Form\nValidated + aria alert"] G --> J H --> J style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style C fill:#181818,stroke:#1e1e1e,color:#888 style D fill:#181818,stroke:#1e1e1e,color:#888 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#181818,stroke:#1e1e1e,color:#888 style H fill:#181818,stroke:#1e1e1e,color:#888 style I fill:#181818,stroke:#1e1e1e,color:#888 style J fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0

Challenges & Solutions

WCAG Colour Contrast Requirements. The initial design used a mid-range blue (#3b82f6) as the primary colour on white backgrounds — a ratio of only 3.0:1, which fails WCAG AA for normal text. Adjusting to #1d5fa6 achieved a 5.1:1 ratio without significantly changing the intended visual feel. All muted text colours were also checked; --color-muted was darkened from #6b7280 to #5a6474 to cross the 4.5:1 threshold on white. Every colour decision was validated with the WebAIM Contrast Checker before being committed.

Structured Product Data for SEO. Adding schema.org/Product markup required careful decisions about which properties to include without a backend to generate structured data dynamically. The solution was to embed itemscope, itemtype, and itemprop attributes directly in the HTML product cards, manually. While this means updates require editing HTML, it keeps the site fully static and is validated using Google's Rich Results Test tool.

What I Learned

  • Accessibility is most effectively treated as a design constraint from the start — retrofitting ARIA roles and contrast fixes after the fact is much harder than planning landmark structure and colour ratios upfront
  • WCAG AA requires a 4.5:1 contrast ratio for normal text, 3:1 for large text and UI elements; these ratios must be verified with a tool, not estimated visually
  • aria-live="polite" on a live region is the correct mechanism for announcing dynamic DOM changes (search results, filter updates) to screen readers without interrupting ongoing reading
  • role="alert" on form error messages triggers an immediate screen reader announcement — appropriate for errors, but not for status updates where polite is less disruptive
  • Embedding structured schema.org/Product data in static HTML is viable for small catalogs and can be validated with Google's Rich Results Test
  • The aspect-ratio property on an iframe wrapper with a 100% fill child is the cleanest CSS-only approach to responsive embedded maps and videos
HTML5 CSS3 Responsive Accessibility Healthcare Business Website