Component

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).

1Alisson30J. Frimpong5I. Konaté4CV. van Dijk6M. Kerkez8D. Szoboszl…10A. Mac Alli…11M. Salah7F. Wirtz18C. Gakpo22H. Ekitiké
Stories

Canonical behaviors.

Zero-Config Vertical

Pass just a formation key and Campos renders position-coded placeholders in the canonical slot order.

GKRBRCBLCBLBCDMRCMLCMRWLWST
Half-Crop Horizontal

Single-team crops are for scouting-card layouts. The goalkeeper stays inside the visible box.

GKRBRCBLCBLBCDMRCMLCMRWLWST
Secondary Pitch Theme

Formation should stay publishable when the pitch surface changes. Themeability lives on the delegated Stadia pitch props, not on a chart-local theme API.

1Raya2White12Saliba6Gabriel33Calafiori41Rice8Ødegaard23Merino7Saka29Havertz11Martinelli
Real Opta Lineup — Liverpool

Decoded from a raw Opta lineup event via fromOpta.formations(). The provider slot IDs are normalized onto the mplsoccer-derived formation table before rendering.

1Alisson30J. Frimpong5I. Konaté4V. van Dijk6M. Kerkez8D. Szoboszl…10A. Mac Alli…11M. Salah7F. Wirtz18C. Gakpo22H. Ekitiké
Dual-Team Match Card — Liverpool vs Tottenham

Full-pitch match card. Home sits in the defensive half; away is mirrored into the attacking half.

1Alisson Bec…8Dominik Szo…26Andy Robert…10Alexis Mac …2Joe Gomez4Virgil van …30Jeremie Fri…38Ryan Graven…18Cody Gakpo7Florian Wir…73Rio Ngumoha1Guglielmo V…23Pedro Porro24Djed Spence14Archie Gray4Kevin Danso3Radu Dragus…38Souza29Pape Matar …19Dominic Sol…9Richarlison11Mathys Tel
Liverpool
Tottenham
Photo And Event Badges

Photo glyph plus event badges. This proves Formation can host richer marker presets without owning the slot logic itself.

RRaya17.2RayaWWhite26.8WhiteSSaliba127.5SalibaGGabriel67.1GabrielCCalafiori335.967'CalafioriRRice417.9RiceØØdegaard87.7CØdegaardMMerino236.9MerinoSSaka78.2SakaHHavertz296.476'HavertzMMartinelli117.0Martinelli
Nationality, Age, And Value

Shirt glyph with nationality, age, and value. Same formation surface, different marker composition.

129€40MESPRaya227€60MENGWhite1224€80MFRASaliba627€65MBRAGabriel3323€50MITACalafiori4126€105MENGRice826€90MNORØdegaard2329€35MESPMerino723€140MENGSaka2925€75MGERHavertz1123€80MBRAMartinelli
Multilingual Stress

Mixed-script labels across Latin diacritics, Japanese, Korean, and Arabic. This is the label-fitting edge case the page should keep honest.

1Ł. Fabiański18Tomiyasu5Söyüncü12Saliba17Zinchenko8Ødegaard6Dahoud11三笘25Müller7Son10صلاح
Static Export

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.

1Raya2White12Saliba6Gabriel33Calafiori41Rice8Ødegaard23Merino7Saka29Havertz11MartinelliArsenal
Recipes

Composition patterns.

Legend Overlay

Use overlay legend placement when the match card has tighter vertical space or needs to sit inside existing editorial chrome.

1Alisson Bec…8Dominik Szo…26Andy Robert…10Alexis Mac …2Joe Gomez4Virgil van …30Jeremie Fri…38Ryan Graven…18Cody Gakpo7Florian Wir…73Rio Ngumoha1Guglielmo V…23Pedro Porro24Djed Spence14Archie Gray4Kevin Danso3Radu Dragus…38Souza29Pape Matar …19Dominic Sol…9Richarlison11Mathys Tel
Liverpool
Tottenham
Bench Composition — Vertical

Single-team benches render outside the pitch and reuse the same shared badge primitives as the on-pitch markers.

1Raya2White12Saliba6Gabriel33Calafiori41Rice8Ødegaard23Merino7Saka29Havertz11Martinelli
Bench Composition — Horizontal

Horizontal single-team layouts move the bench to a bottom strip by default.

1Raya2White12Saliba6Gabriel33Calafiori41Rice8Ødegaard23Merino7Saka29Havertz11Martinelli
Cross-cutting

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.

Responsive

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.

1Raya2White12Saliba6Gabriel33Calafiori41Rice8Ødegaard23Merino7Saka29Havertz11Martinelli
Normal
12126334182372911
Small
12126334182372911
Smallest (small multiples)
Usage

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} />;
}
                  
API

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.
Use with AI
LLM Prompt
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.
Pitch