Waqas Bajwa
Same H1 style as the home page:
Public Sans Light, tracking -2%
The breadcrumb is a nav landmark named Breadcrumb. The last item is marked as the current page.

Rebuilding the V/Line website

V/Line is the Victorian Government's regional transport service. I am the only frontend developer on the rebuild of its public website. I own the frontend architecture and built the design system and component library from scratch.

Laid out like an inspect panel:
mono labels, 1px rules.
A description list, so each label is read with its value.
Role
Sole frontend developer
Team
Tech Lead, 2 backend developers, 2 senior software developers, a BA, a tester, a Product Owner and a Project Manager. No designer.
When
Mar 2026 to now
Stack
Kentico Xperience 13, Pattern Lab, Handlebars, Tailwind CSS v4, Tailwind Plus Elements, Alpine.js, TypeScript, Vite
Tools
Figma, Azure DevOps, BrowserStack, axe, Datadog
Status
Not live yet, so there are no screenshots. I am happy to walk through it in an interview.

The brief

The old site ran on Kentico Portal Engine and was not built for accessibility. The rebuild had two goals: move to Kentico Xperience 13 on MVC, and meet WCAG 2.2 AA in full.

The designs were finished in Figma before I joined, and the team has no designer. When a design did not cover a state, a content case or a keyboard path, I decided how it should work from my own experience and from requirements I gathered with stakeholders.

How the system is built

Counted from the Pattern Lab folders,
one per component

45+components, plus 4 page templates and 7 page types, each with its own documentation page

  • Components are organised with atomic design in Pattern Lab: atoms, molecules, organisms, templates and pages.
  • Tailwind CSS v4 for styles, with component classes built through @apply and named with BEM.
  • Alpine.js for behaviour, chosen as a lightweight framework, with TypeScript modules for the complex parts.
  • Tailwind Plus Elements for the dialog and the accordions. I added screen reader announcements the defaults did not give.
  • Vite builds the CSS and JavaScript that Kentico serves.

Colour tokens that failed once built

The Figma file used Tailwind v3 colour tokens. The build uses Tailwind v4, and the two versions do not produce the same colours. A token with the same name could render a different colour, so text that passed contrast in the design failed it on the page.

20+colour tokens adjusted to pass WCAG 2.2 AA, checked one by one with the tester

I traced the failures to the version change and explained the cause to the team. I adjusted the values with the tester checking each one, and the team agreed the new values, with me owning them. The fix sits in the tokens, so every component that uses them inherits it.

An accessible date picker

The journey planner needs a departure date and a return date. A date picker is one of the hardest components to make accessible: a grid of dates has to work with a keyboard and still make sense when a screen reader reads it aloud.

I ran a spike and compared five options: the browser's native date input, Cally, Vanilla Calendar Pro, the Flowbite datepicker and the daisyUI calendar styles. I also checked how date pickers are handled with Tailwind Plus Elements. Cally, a calendar web component, fitted our use case best and had accessibility in its base.

I built a prototype on Cally with these decisions:

  • Separate fields for the departure and return dates, with no range selector.
  • The date can still be typed. The calendar is an extra way in, not the only one.
  • Dates before the earliest allowed date cannot be picked.

The prototype was a good base, but it was not accessible yet. My spike report listed what it needed, and I built all of it into the final component:

aria-describedby ties the format hint to the input, so a screen reader reads it with the label.
  • aria-expanded on the button that opens the calendar.
  • An explicit invalid state on the input, with the error text announced every time.
  • Focus returning to the same place however the calendar closes.
  • A visible format hint, linked to the input with aria-describedby.
  • Clear announcements when the month or the selected date changes.
  • Testing with assistive technology on mobile and desktop, and checks for reflow, zoom and target size after final styling.

The final date picker has passed testing and is ready for release. The team was happy with the outcome, and it is the work I am proudest of on this project.

Traffic Light Tool

The Traffic Light Tool shows the live status of each train line. Geelong, for example, shows either good service or a disruption. Each status uses colour, an icon and text, so none of it depends on colour alone.

Selecting a line opens a dialog with a card for each disruption. Each card is a molecule in the design system. Selecting a card moves to a second screen in the same dialog with the full details. The status comes from a live feed and is rendered on the server.

A real table with a caption and column headers, so a screen reader can read it row by row.
Where keyboard focus goes
When someoneFocus moves to
Selects a line, for example GeelongThe first disruption card link in the dialog
Selects a disruption cardThe back link on the details screen

A table of contents with no design

The business needed a table of contents for long pages, but there was no design for it. The obvious option was two widgets: one for the links and one for each section. Editors would have added both to every page, matched their ids by hand and kept them free of duplicates.

I gathered the requirements with stakeholders and kept the backend work to one template. My JavaScript does the rest:

The contents list on this page works the same way.
  1. An editor publishes a page with h2 headings.
  2. The script gives each h2 an id.
  3. It builds a linked table of contents at the top of the page, inside a nav landmark, so screen reader users can find it.

Editors assign the template and write their headings. Nothing else needs maintaining. For content editors, it has been one of the project's clear wins.

Accessibility results in the docs

Test results help only if people see them. I wrote a script that runs axe through BrowserStack and Selenium against the Pattern Lab pages, one group at a time: atoms, molecules, organisms, templates or everything. It saves a report for each run and writes the result into each component's own documentation page.

A developer opening a component's docs sees its accessibility status next to its contract, without running anything.

"No violations found by axe" is the honest claim. Automated tools cannot prove a page is fully accessible.

116of 116 component docs show an accessibility status. axe found no violations across the 127 stored reports.

Automated checks cover only part of WCAG, so I also test with a keyboard and a screen reader, and work with the tester on audits and fixes.

Handing components to the backend team

Every component has a contract: its fields, variants and states. Each one has a Markdown page in Pattern Lab with usage, Kentico notes and the rules for missing content, for example what renders when there is a location but no date.

headingLevel lets the same component sit anywhere without breaking the heading order.
  • id
  • heading
  • headingLevel
  • ariaTitle
  • variant
  • icon

Backend developers copy the Handlebars template from Pattern Lab, convert it to Razor and use the assets Vite builds. They can build a page without asking me how a component should behave.

Smaller problems worth solving

Media playlist

The old video playlist had no accessibility. The new one fits any screen size, works with the keyboard through tabs, and tells screen reader users where they are: "Video 2 of 5, now playing".

Icons without layout shift, and a faster first screen

The site loads Google Material Symbols from a CDN. Until the font arrives, each icon shows as its name in plain text, which pushed the layout around. I found it in Lighthouse and fixed it in CSS: each icon has a fixed width and height, and the text is clipped while it loads. I also added critical CSS for the content above the fold, so the first screen renders without waiting for the full stylesheet. I tested it on Slow 3G and Slow 4G.

0Cumulative Layout Shift, down from 0.244. Speed Index went from 6.1 s to 1.3 s.

Also built

Navigation with search and typeahead, a keyboard-operable carousel that never autoplays, and the shared dialog the Traffic Light Tool uses.

How I work with the team

I lead frontend estimation, story planning and technical spikes, like the one behind the date picker. I work in Azure DevOps with the rest of the team, use Datadog to look into problems in the browser, and show developers, BAs and content editors how to use the system.

What I learned

The table of contents changed how I judge a solution. I now ask what it will cost to maintain, as well as what it will cost to build. I apply the same test to packages: before adding one, I check whether the platform can already do the job.