Fixtures in your timezone, the live table, season stats, and every final table since 1992.
Sports & fitnessJavaScript · CSSGitHub @ismaycOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Unofficial Premier League fixture viewer — kickoff times in your timezone, where to watch, the live table with home/away splits, season stats with player leaderboards, team sheets & lineups, calendar export, and every final table since 1992-93. React + Vite.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A React + Vite web app showing all 380 matches of the Premier League season in
your timezone, with where to watch, the live table, season statistics,
team sheets, and every final table since the competition began in 1992-93.Read the full README ↗A React + Vite web app showing all 380 matches of the Premier League season in your timezone, with where to watch, the live table, season statistics, team sheets, and every final table since the competition began in 1992-93.
🔗 Live: https://premier-league-viewer.netlify.app · https://ismayc.github.io/premier-league/
.ics export, optionally
filtered to the clubs you follow.| View | What it shows |
|---|---|
| Fixtures | The season as a chronological list, grouped by day, with a next-kickoff countdown. Filter to followed clubs, show or hide played matches, export to calendar. |
| Week | A Monday-to-Sunday grid. Makes an empty midweek and a congested festive period legible at a glance. |
| Table | The live league table, with home/away splits, recent form, and European / relegation bands. |
| Stats | League totals, player leaderboards across ten categories with a season switcher, per-player biographies, and a per-club goal-difference chart for any season. |
| History | Every final table since 1992-93, an all-time table, and any club's finishing position season by season. |
npm install
npm run dev
npm test runs the suite; npm run test:coverage adds the coverage report;
npm run build produces a static dist/.
The suite is held at 100% coverage — statements, branches, functions and
lines — and vite.config.js sets thresholds so CI fails if anything ships
untested. Test files run serially (fileParallelism: false) because Vitest's
v8 coverage provider races when several workers finish at once and dies trying
to read a temp file that has already been cleaned up.
The suite runs against frozen data. A plugin in vite.config.js resolves
every import of the four modules the refresh rewrites (fixtures.js,
players.js, teams.js, and history.js in src/data/) to a frozen copy in
test/fixtures/frozen/, so a twice-daily refresh cannot move the coverage gate.
The real modules have their own suite: npm run test:data runs test/live/
(invariants on the fixtures, the table, the leaderboards, and every historical
season, plus a smoke render of every view) with no coverage threshold. That
live suite is the gate the refresh workflow runs before it commits new data,
and CI runs it on every push too.
The entire season, every historical table, and the player leaderboards are committed to the repository as generated JavaScript modules. The app therefore renders completely with no network request at all. A small live overlay polls ESPN's scoreboard for matches in progress and patches scores in on top; if that fetch fails, the app is stale rather than broken.
scripts/fetch-fixtures.mjs → src/data/teams.js, src/data/fixtures.js, public/logos/
scripts/fetch-stats.mjs → src/data/players.js
scripts/fetch-history.mjs → src/data/history.js
Refresh everything with npm run fetch:all. The scripts use Node built-ins
only — no imports from node_modules — so a scheduled refresh can run
without an npm ci first.
ESPN's standings archive only reaches 2002-03. openfootball publishes match
results back to 1992-93, so fetch-history.mjs parses those results and
computes each table itself, applying the same ordering rules the live table
uses — points, then goal difference, then goals scored. The Premier League has
never used head-to-head to separate clubs for a placing.
That makes the tables verifiable rather than merely copied. Before writing,
every season is checked against invariants that must hold for any correct
parse: total appearances equal twice the match count, goal difference across
the league sums to zero, and points equal three per decisive match plus two
per draw. A season that fails is reported rather than silently written. Those
same invariants run again in test/history-data.test.js over the committed
data, so a bad refresh fails the build instead of shipping a wrong table.
No router, no state library. The set of things a viewer can choose — view,
timezone, club, whether scores are hidden — is small enough to live in
useState and serialise into the query string, which has the useful property
that any state worth reaching is shareable. Only non-default values are
written, so a first-time URL stays clean.
One impure boundary. App.jsx fetches and merges; everything downstream is
a pure function of the merged fixture list. That is why nearly all the logic is
testable without a DOM.
Data-mark colours are separate from UI colours. The accent never encodes data, so a coloured mark is never mistaken for a control. The chart and league-zone palettes were checked for colour-vision separation and contrast against both the light and dark surfaces, and colour never carries meaning alone — positions are always printed, zones are named in a legend, and every bar is labelled with its value.
International breaks are inferred, not fetched. The feed carries this
competition and nothing else: no field says the players are away with their
countries. What the fixture list does carry is the hole, and src/utils/breaks.js
calls one an international break on two conditions — it runs at least eleven
clear days, and its midpoint falls in a month FIFA holds a men's window
(September, October, November, March, June). The league's own rhythm is weekly
and a cup weekend or the mid-season break opens a week or a little more: in the
2026-27 list the domestic holes run 7, 8 and 9 clear days and the international
ones 13, 19 and 20, so the threshold sits in open space between the two
populations rather than splitting either. Both tests describe the shape of a league calendar rather than one
season's dates, so a refreshed fixture list re-derives its own breaks. What the
app never claims is which countries are playing or when the window itself opens:
the note says only what the fixture list can support — no league football for
N days, back on this date.
Kickoffs are stored as UTC instants, never as wall-clock strings. Premier League times are published in UK time, which is exactly what a naive "3pm Saturday" gets wrong for anyone outside Britain twice a year when the clocks shift. The detail view shows both your local time and UK time so the app can be checked against any published fixture list.
Broadcast listings come from ESPN's US-region feed, so they name US broadcasters (NBC, Peacock, USA Network) rather than UK ones.
Created by Chester Ismay. Source on GitHub. Companion to the World Cup 2026 schedule viewer.
The favicon lion is Lion by Lorc from game-icons.net, used under CC BY 3.0 and recoloured to the app's accent. It is not the Premier League's crowned-lion mark.
An unofficial, non-commercial fan project. Not affiliated with, endorsed by, or sponsored by the Premier League. "Premier League", and club, broadcaster, and competition names and crests are trademarks of their respective owners. Fixtures, statistics and live scores come from ESPN; historical results come from the public-domain OpenFootball project.
Licensed under the MIT License.