Formation
Lineup diagrams and dual-team cards with publishable defaults. Single-team mode is for lineup previews and scouting crops; dual-team mode is for match cards. Hero fixture: Liverpool vs AFC Bournemouth (2025-08-15, Premier League).
Canonical behaviors.
Pass just a formation key and Campos renders position-coded placeholders in the canonical slot order.
Single-team crops are for scouting-card layouts. The goalkeeper stays inside the visible box.
Formation should stay publishable when the pitch surface changes. Themeability lives on the delegated Stadia pitch props, not on a chart-local theme API.
Decoded from a raw Opta lineup event via fromOpta.formations(). The provider slot IDs are normalized onto the mplsoccer-derived formation table before rendering.
Full-pitch match card. Home sits in the defensive half; away is mirrored into the attacking half.
Photo glyph plus event badges. This proves Formation can host richer marker presets without owning the slot logic itself.
Shirt glyph with nationality, age, and value. Same formation surface, different marker composition.
Mixed-script labels across Latin diacritics, Japanese, Korean, and Arabic. This is the label-fitting edge case the page should keep honest.
Formation is supported by the static export pipeline, but only through the bounded export-safe subset: constant marker styling, no substitutes bench, and no custom slot renderers or photo glyphs.
Composition patterns.
Use overlay legend placement when the match card has tighter vertical space or needs to sit inside existing editorial chrome.
Single-team benches render outside the pitch and reuse the same shared badge primitives as the on-pitch markers.
Horizontal single-team layouts move the bench to a bottom strip by default.
Shared concerns.
Data Contract
Formation expects canonical formation keys and normalized player assignments.
Provider parsing lives in adapters like
fromOpta.formations() and fromWhoScored.formations().
Marker Ownership
The slot system belongs to the marker primitives layer. Use this page to understand how presets compose onto formations, and use Marker Primitives for the slot model itself.
Validation
Unknown formation strings throw loudly at construction time. Campos does not silently fall back to a default shape.
Themeability
The pitch theme stays delegated to Stadia through pitchTheme and
pitchColors. Formation should keep its lineup semantics intact
when the surface styling changes.
Static Export
Formation is in the stable export contract, but only through the bounded subset:
constant markers / markerLabels, no bench, and no
custom slot renderers, photo glyphs, or badge-prefix branches.
Reference Catalogue
The full 68 mplsoccer shapes and the Opta-supported subset now live on Formation Reference, so this page can stay focused on component behavior.
Normal, small, smallest.
Formation scales cleanly down to small multiples. At the smallest size the example drops names and in-marker labels to keep the shape legible.
Best-practice examples.
Minimal usage
Zero-config works with just a formation key. Player parsing and provider decoding stay outside the component.
import { Formation } from "@withqwerty/campos-react";
export function PreviewFormation() {
return <Formation formation="4-3-3" />;
}
Single-team scouting card
Half-pitch crops are for single-team lineup cards. Keep the data normalized, then change crop and direction explicitly.
import { Formation } from "@withqwerty/campos-react";
import type { FormationPlayer } from "@withqwerty/campos-react";
type Props = {
players: FormationPlayer[];
};
export function TeamScoutingCard({ players }: Props) {
return (
<Formation
formation="4-2-3-1"
players={players}
teamColor="#c8102e"
crop="half"
side="attack"
/>
);
}
Dual-team lineup card
For full-pitch match cards, pass explicit home and away team specs. Campos handles mirroring, halves, and legend layout.
import { Formation } from "@withqwerty/campos-react";
import type { FormationTeamSpec } from "@withqwerty/campos-react";
type Props = {
home: FormationTeamSpec;
away: FormationTeamSpec;
};
export function MatchLineups({ home, away }: Props) {
return <Formation home={home} away={away} />;
}
Public surface.
| Prop | Type | Default | Description |
|---|---|---|---|
formation | FormationKey | required (single-team) | Formation string. Accepts hyphenated ("4-3-3") or compact ("433") forms and resolves against the mplsoccer-derived table. |
players | readonly FormationPlayer[] | placeholders | Optional starter assignments. Omit to render zero-config placeholder markers in canonical formation slots. |
teamColor | string | theme accent | Marker fill color for single-team mode. Dual-team mode uses the per-team color on each team spec. |
teamLabel | string | — | Optional team label used in accessibility text and single-team legend copy. |
orientation | "vertical" | "horizontal" | "vertical" | Vertical attacks upward; horizontal attacks rightward. Dual-team cards support both orientations. |
crop | "full" | "half" | "full" | Single-team crop mode. Half-pitch views are for scouting-card style lineups and are not used in dual-team mode. |
side | "attack" | "defend" | "attack" | Visible half when crop='half'. Attack is top or right; defend is bottom or left. |
flip | boolean | auto | Mirrors the formation direction. On half-pitch crops the auto behavior keeps the goalkeeper inside the visible box. |
home | FormationTeamSpec | — | Dual-team bottom-half team spec. Requires at minimum a formation, and can include players, color, label, and substitutes. |
away | FormationTeamSpec | — | Dual-team top-half team spec. Campos mirrors the shape so the two forward lines face each other. |
markers / markerLabels / markerBadges | style objects | theme-driven defaults | First-class styling surface for glyph kind, marker fill/stroke, name-pill text/background, and prefix badges. |
markerComposition | FormationMarkerComposition | — | Advanced composition escape hatch for custom glyph renderers and slot content. Use this when the first-class styling props are not enough. |
substitutes | readonly FormationPlayer[] | — | Single-team bench rendered outside the pitch. Placement defaults follow orientation but can be overridden. |
substitutesPlacement | "left" | "right" | "top" | "bottom" | orientation-driven | Override the bench position for single-team mode. Vertical defaults right; horizontal defaults bottom. |
legendPlacement | "bottom" | "top" | "top-left" | "top-right" | "none" | "bottom" | Dual-team legend position. Use overlay placements when the card sits in a tighter editorial layout. |
showLabels | boolean | true | Toggle the in-marker labels. |
showNames | boolean | true | Toggle the below-marker name pills. |
labelStrategy | "auto" | "positionCode" | "jerseyNumber" | "initials" | "name" | "auto" | Controls which label source is rendered inside the marker. "auto" picks the best available source per player. |
pitchTheme | "primary" | "secondary" | "primary" | Delegated Stadia surface preset. |
pitchColors | PitchColors | — | Direct pitch color overrides for branded or editorial surfaces. |
Create a React component using Campos Formation. Import Formation from @withqwerty/campos-react, keep provider parsing outside the component, show the smallest good usage first, then one single-team crop example and one dual-team lineup example.