FIFA 2026 World Cup
Schedule and results
This page rebuilds about every 10 minutes during match windows, with live scores from football-data.org’s paid API tier. An in-progress match shows a running scoreline (e.g. “1–0 (live)”). Because the page is a periodic snapshot, the scoreline can trail the live match by a few minutes.
Last rebuilt: June 12, 2026 at 03:08 EDT
localTz = Intl.DateTimeFormat().resolvedOptions().timeZone
tzOptions = new Map([
[`Your local time (${localTz})`, localTz],
["UTC", "UTC"],
["US Eastern", "America/New_York"],
["US Central", "America/Chicago"],
["US Mountain", "America/Denver"],
["US Pacific", "America/Los_Angeles"],
["Mexico City", "America/Mexico_City"],
["São Paulo", "America/Sao_Paulo"],
["London", "Europe/London"],
["Central Europe (Paris/Berlin/Madrid)", "Europe/Paris"],
["Athens / Cairo", "Europe/Athens"],
["Moscow", "Europe/Moscow"],
["Dubai", "Asia/Dubai"],
["India", "Asia/Kolkata"],
["Tokyo / Seoul", "Asia/Tokyo"],
["Beijing / Singapore", "Asia/Shanghai"],
["Sydney", "Australia/Sydney"],
["Auckland", "Pacific/Auckland"]
])
viewof selectedTz = Inputs.select(tzOptions, {label: "Time zone", value: localTz})
tz = selectedTzPick your time zone above — both tabs update. (Defaults to your device’s time zone.)
selectedTeam = teams.find(t => t.name === selectedTeamName)
teamMatches = matches.filter(
m => m.home_team_id === selectedTeam.id || m.away_team_id === selectedTeam.id
)
nextMatch = teamMatches.find(
m => (m.utc_date >= now || liveStatuses.has(m.status)) &&
!finishedStatuses.has(m.status) &&
!inactiveStatuses.has(m.status)
)
pastMatches = teamMatches.filter(
m => m.utc_date < now &&
!liveStatuses.has(m.status) &&
!inactiveStatuses.has(m.status)
)
upcoming = teamMatches.filter(
m => (m.utc_date >= now || liveStatuses.has(m.status)) &&
!finishedStatuses.has(m.status) &&
!inactiveStatuses.has(m.status)
)Next match
Upcoming matches
Past results
liveStatuses = new Set(["IN_PLAY","PAUSED","EXTRA_TIME","PENALTY_SHOOTOUT"])
finishedStatuses = new Set(["FINISHED","AWARDED"])
inactiveStatuses = new Set(["CANCELLED","POSTPONED","SUSPENDED"])
teamById = new Map(teams.map(t => [t.id, t]))
function crestFor(name, id) {
const t = teamById.get(id);
return t
? html`<img src="${t.crest_url}" alt="" class="crest-sm"> ${name}`
: html`<span class="tbd">${name ?? "TBD"}</span>`;
}
function groupLabelFor(team) {
const m = matches.find(
x => (x.home_team_id === team.id || x.away_team_id === team.id) &&
x.stage === "GROUP_STAGE"
);
return m && m.group ? `Group ${m.group.replace("GROUP_", "")}` : "—";
}
function stageLabel(stage, group) {
if (!stage) return "";
if (stage === "GROUP_STAGE") return group ? `Group ${group.replace("GROUP_", "")}` : "Group stage";
return stage.replace(/_/g, " ").toLowerCase()
.replace(/\b\w/g, c => c.toUpperCase());
}
// All times follow the selected `tz` (see the time-zone picker). Building a
// fresh formatter per call keeps things reactive when `tz` changes.
function dayKey(d) {
// e.g. "2026-06-12" — the calendar date in the selected zone.
return new Intl.DateTimeFormat("en-CA", {timeZone: tz}).format(d);
}
function formatDate(d) {
return d.toLocaleString("en-US", {
weekday: "long", month: "long", day: "numeric", year: "numeric",
hour: "numeric", minute: "2-digit", timeZone: tz, timeZoneName: "short"
});
}
function formatDateShort(d) {
return d.toLocaleDateString("en-US", {weekday: "short", month: "short", day: "numeric", timeZone: tz});
}
function formatTime(d) {
return d.toLocaleTimeString("en-US", {hour: "numeric", minute: "2-digit", timeZone: tz, timeZoneName: "short"});
}
function formatDayHeading(isoDay) {
// isoDay is already the selected-zone calendar date; format it as UTC midday
// so the weekday/month/day read back out without a second timezone shift.
const d = new Date(isoDay + "T12:00:00Z");
return d.toLocaleDateString("en-US", {weekday: "long", month: "long", day: "numeric", year: "numeric", timeZone: "UTC"});
}
function statusBadge(m) {
if (liveStatuses.has(m.status)) return html`<span class="badge live">LIVE</span>`;
if (m.status === "POSTPONED") return html`<span class="badge postponed">Postponed</span>`;
if (m.status === "CANCELLED") return html`<span class="badge cancelled">Cancelled</span>`;
if (m.status === "SUSPENDED") return html`<span class="badge suspended">Suspended</span>`;
return "";
}
function nextMatchCard(m, team) {
if (!m) {
return html`<div class="next-card empty">No upcoming matches on the schedule.</div>`;
}
const isHome = m.home_team_id === team.id;
const opp_id = isHome ? m.away_team_id : m.home_team_id;
const opp_t = teamById.get(opp_id);
const oppName = isHome ? m.away_team : m.home_team;
return html`
<div class="next-card">
<div class="next-date">${formatDate(m.utc_date)}</div>
<div class="next-matchup">
<div class="opponent">
${opp_t ? html`<img src="${opp_t.crest_url}" class="crest-md" alt="">` : ""}
<div>
<div class="vs-label">${isHome ? "vs" : "at"}</div>
<div class="opponent-name">${oppName ?? "TBD"}</div>
</div>
</div>
<div class="next-stage">${stageLabel(m.stage, m.group)}${statusBadge(m)}</div>
</div>
${m.venue ? html`<div class="next-venue">${m.venue}</div>` : ""}
</div>`;
}
// `score_display` from R is always home–away. On a team page we want the
// selected team's goals first, so the same match reads as a win for one
// side and a loss for the other. Non-numeric strings ("in progress",
// "no score available yet", "postponed", "") pass through unchanged.
function flipScore(s) {
if (!s) return s;
const m = s.match(/^(\d+)–(\d+)(\s*\((?:(\d+)–(\d+)\s*PK|live)\))?$/);
if (!m) return s;
const [, h, a, suffix, pkh, pka] = m;
let out = `${a}–${h}`;
if (suffix) {
out += pkh !== undefined ? ` (${pka}–${pkh} PK)` : ` (live)`;
}
return out;
}
function scheduleTable(rows, team) {
return html`<table class="schedule">
<thead>
<tr>
<th>Date</th><th>Kick-off</th><th>Opponent</th><th>H/A</th>
<th>Stage</th><th>Score</th>
</tr>
</thead>
<tbody>
${rows.map(m => {
const isHome = m.home_team_id === team.id;
const opp_id = isHome ? m.away_team_id : m.home_team_id;
const oppName = isHome ? m.away_team : m.home_team;
const score = isHome ? m.score_display : flipScore(m.score_display);
return html`<tr>
<td>${formatDateShort(m.utc_date)}</td>
<td>${formatTime(m.utc_date)}</td>
<td>${crestFor(oppName, opp_id)}</td>
<td>${isHome ? "H" : "A"}</td>
<td>${stageLabel(m.stage, m.group)}</td>
<td class="score">${score || ""} ${statusBadge(m)}</td>
</tr>`;
})}
</tbody>
</table>`;
}
function matchListTable(rows) {
return html`<table class="day-table">
<tbody>
${rows.map(m => html`<tr>
<td class="time">${formatTime(m.utc_date)}</td>
<td class="team home">${crestFor(m.home_team, m.home_team_id)}</td>
<td class="vs">vs</td>
<td class="team away">${crestFor(m.away_team, m.away_team_id)}</td>
<td class="stage">${stageLabel(m.stage, m.group)}</td>
<td class="score">${m.score_display || ""} ${statusBadge(m)}</td>
</tr>`)}
</tbody>
</table>`;
}Data: football-data.org · Code: worldcup26 R package