'use client'; import { useState, useEffect } from 'react'; import styles from './AdmissionsView.module.css'; /* ─── Date helpers ─────────────────────────────────────── */ function daysUntil(month: number, day: number): number { const today = new Date(); today.setHours(0, 0, 0, 0); const y = today.getFullYear(); let target = new Date(y, month - 1, day); if (target < today) target = new Date(y + 1, month - 1, day); return Math.round((target.getTime() - today.getTime()) / 86_400_000); } function nextDate(month: number, day: number): Date { const today = new Date(); today.setHours(0, 0, 0, 0); const y = today.getFullYear(); const target = new Date(y, month - 1, day); if (target <= today) return new Date(y + 1, month - 1, day); return target; } function fmtDate(month: number, day: number): string { return nextDate(month, day).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric', }); } /* ─── Data ─────────────────────────────────────────────── */ interface Chip { type: 'deadline' | 'offer'; track: string; milestone: string; month: number; day: number; } const CHIPS: Chip[] = [ { type: 'offer', track: 'Primary · Offer Day', milestone: 'Primary National Offer Day', month: 4, day: 16 }, { type: 'deadline', track: 'Secondary · Deadline', milestone: 'Secondary applications close', month: 10, day: 31 }, { type: 'deadline', track: 'Primary · Deadline', milestone: 'Primary applications close', month: 1, day: 15 }, { type: 'offer', track: 'Secondary · Offer Day', milestone: 'Secondary National Offer Day', month: 3, day: 1 }, ]; interface Step { date?: string; title: string; body: string; highlight?: 'deadline' | 'offer'; /* Easy-to-miss, deadline-sensitive warnings surfaced as callout boxes rather than buried in the body prose. */ callouts?: { title: string; body: string }[]; /* How SchoolCompare helps at this stage of the journey */ tool?: { label: string; href: string }; } const SECONDARY_STEPS: Step[] = [ { title: 'Check entry criteria', body: 'Look at each school\'s admissions policy. Catchment areas, faith criteria, sibling priority and aptitude tests vary widely.', callouts: [ { title: 'Selective schools register earlier', body: 'Grammar and other selective schools need separate entrance-test registration months earlier, often by September and well before the 31 October deadline.', }, { title: 'Faith schools may need an extra form', body: 'Many faith schools require a supplementary information form (SIF) sent directly to the school, on top of the council application.', }, ], tool: { label: 'Find schools & view their admissions history', href: '/' }, }, { date: 'September', title: 'Portal opens', body: 'Your local council opens its online admissions portal. Register early to avoid last-minute technical issues. You apply through your home council even if you prefer schools in neighbouring boroughs.', tool: { label: 'Compare schools side by side', href: '/compare' }, }, { date: '31 October', title: 'Application deadline', body: 'Submit your ranked list of schools. Most councils allow three to six preferences (London boroughs typically six). Councils treat all preferences equally, so list schools in the genuine order you want them, not strategically.', highlight: 'deadline', tool: { label: 'Compare performance to order your preferences', href: '/rankings' }, }, { date: '1 March', title: 'National Offer Day', body: 'Your allocated school appears on the council\'s online admissions portal and is sent by email. Release times are set by each council (often from late afternoon, though some publish overnight), so check your council\'s page rather than refreshing at midnight.', highlight: 'offer', tool: { label: 'Look up your allocated school', href: '/' }, }, { date: '~15 March', title: 'Accept or decline', body: 'Respond by the deadline your council gives, typically around 15 March. Accepting does not prevent you from keeping a place on a waiting list for a preferred school.', tool: { label: 'Weigh your offer against your other choices', href: '/compare' }, }, { title: 'Appeals', body: 'If unsuccessful, you can appeal within 20 school days of the refusal letter. Secondary appeals consider whether prejudice to the school outweighs your case, and success rates vary.', tool: { label: 'Gather performance evidence for your case', href: '/rankings' }, }, ]; const PRIMARY_STEPS: Step[] = [ { title: 'Research entry criteria', body: 'Faith schools, language units, and distance-based catchments differ by school. Start by reading each school\'s admissions policy on their website or the council\'s website.', callouts: [ { title: 'Faith schools may need an extra form', body: 'Many faith schools require a supplementary information form (SIF) sent directly to the school, in addition to the council application.', }, ], tool: { label: 'Find schools & view their admissions history', href: '/' }, }, { date: 'September', title: 'Portal opens', body: 'Apply through your home council\'s portal, even if your preferred school is in another borough. Most councils accept applications from September.', tool: { label: 'Compare schools side by side', href: '/compare' }, }, { date: '15 January', title: 'Application deadline', body: 'List up to 3–6 schools (the number varies by council) in genuine preference order. The equal preference rule means all preferences are considered before any offers are made.', highlight: 'deadline', tool: { label: 'Compare performance to order your preferences', href: '/rankings' }, }, { date: '16 April', title: 'National Offer Day', body: 'Results are published online. Reception offers are sent on 16 April (or the next working day if that falls on a weekend or bank holiday).', highlight: 'offer', tool: { label: 'Look up your allocated school', href: '/' }, }, { date: '~1 May', title: 'Accept or decline', body: 'Respond by your council\'s deadline, typically around 1 May. Accepting secures the place while you wait to see if a preferred school\'s waiting list moves.', tool: { label: 'Weigh your offer against your other choices', href: '/compare' }, }, { title: 'Appeals', body: 'Infant class-size appeals (Reception to Year 2) have a very narrow legal test and a low success rate. For Year 3+, appeals follow the same process as secondary.', tool: { label: 'Gather performance evidence for your case', href: '/rankings' }, }, ]; interface Tip { heading: string; body: string; } const TIPS: Tip[] = [ { heading: 'Equal preference rule', body: 'Councils consider every school you list before making any offer, then give you the highest-ranked one you qualify for. Your order only decides which qualifying school you get. You can\'t game it, so list schools in the order you genuinely want them.', }, { heading: 'Late applications go to the back', body: 'Submit before the deadline even if your child does not turn the required age until later in the year. Late applicants are only considered after all on-time applications.', }, { heading: 'Waiting lists', body: 'You can go on waiting lists for multiple schools simultaneously. Lists are ordered by admissions criteria, not when you joined. They can move significantly over the summer.', }, ]; /* ─── Component ────────────────────────────────────────── */ const NAV_ITEMS = [ { id: 'primary', label: 'Primary' }, { id: 'secondary', label: 'Secondary' }, { id: 'tips', label: 'Tips' }, ] as const; type NavId = typeof NAV_ITEMS[number]['id']; export function AdmissionsView() { const [chipDays, setChipDays] = useState<(number | null)[]>(CHIPS.map(() => null)); const [activeId, setActiveId] = useState('primary'); useEffect(() => { setChipDays(CHIPS.map(c => daysUntil(c.month, c.day))); }, []); useEffect(() => { const sections = NAV_ITEMS .map(item => document.getElementById(item.id)) .filter((el): el is HTMLElement => el !== null); if (sections.length === 0) return; const observer = new IntersectionObserver( (entries) => { const visible = entries .filter(e => e.isIntersecting) .sort((a, b) => b.intersectionRatio - a.intersectionRatio); if (visible[0]) setActiveId(visible[0].target.id as NavId); }, { rootMargin: '-30% 0px -55% 0px', threshold: [0, 0.25, 0.5, 0.75, 1] }, ); sections.forEach(s => observer.observe(s)); return () => observer.disconnect(); }, []); const handleNavClick = (e: React.MouseEvent, id: NavId) => { e.preventDefault(); const el = document.getElementById(id); if (!el) return; const top = el.getBoundingClientRect().top + window.scrollY - 16; window.scrollTo({ top, behavior: 'smooth' }); setActiveId(id); }; return (
{/* In-page nav — sticky left rail on desktop, sticky top pills on mobile */}
{/* Hero */}

School Admissions Guide

Everything parents need to know about applying for a school place in England, from opening dates to National Offer Day, with live countdowns to every key milestone.

{/* Countdown strip */}
Days until next milestone
{CHIPS.map((chip, i) => { const days = chipDays[i]; const isUrgent = days !== null && days <= 14; const chipClass = [ styles.chip, chip.type === 'deadline' ? styles.chipDeadline : styles.chipOffer, isUrgent ? styles.chipUrgent : '', ].filter(Boolean).join(' '); return (
{days === 0 ? 'Today' : (days ?? '—')} {days !== null && days > 0 && days}
{chip.milestone}
{days !== null ? fmtDate(chip.month, chip.day) : ''}
); })}
{/* Primary track */}
Reception entry

Primary school admissions

For children starting Reception (Year R) in September. Applications are submitted in the autumn of the year before entry.

Deadline {fmtDate(1, 15)}
Offer Day {fmtDate(4, 16)}
    {PRIMARY_STEPS.map((step, i) => (
  1. {i < PRIMARY_STEPS.length - 1 &&
    }
    {step.date &&
    {step.date}
    }
    {step.title}

    {step.body}

    {step.callouts && step.callouts.length > 0 && (
    {step.callouts.map((c) => (
    {c.title}

    {c.body}

    ))}
    )} {step.tool && ( {step.tool.label} )}
  2. ))}
{/* Secondary track */}
Year 7 entry

Secondary school admissions

For children starting secondary school (Year 7) in September. Applications are submitted in the autumn of Year 6.

Deadline {fmtDate(10, 31)}
Offer Day {fmtDate(3, 1)}
    {SECONDARY_STEPS.map((step, i) => (
  1. {i < SECONDARY_STEPS.length - 1 &&
    }
    {step.date &&
    {step.date}
    }
    {step.title}

    {step.body}

    {step.callouts && step.callouts.length > 0 && (
    {step.callouts.map((c) => (
    {c.title}

    {c.body}

    ))}
    )} {step.tool && ( {step.tool.label} )}
  2. ))}
{/* Tips */}

Three things most parents get wrong

{TIPS.map((tip, i) => (
{String(i + 1).padStart(2, '0')}

{tip.heading}

{tip.body}

))}
); }