This template implements a generic visual representation of the bracket of a single-elimination tournament with any number of rounds.

{{TeamBracket
| RD1 =
| RD1-seed1   =
| RD1-team1   =
| RD1-score1  =
 ...
}}

Per and , "avoid using smaller font sizes in elements that already use a smaller font size, such as infoboxes, navboxes and reference sections". Since this template's text is already reduced to 90% of the normal size, use of the {{Small}} template or <small>...</small> tags within this template produces a font size that is 77% of the page default, well below the 85% minimum specified in the linked guidelines.

Parameter Description
maxround maximum round to display. This parameter should be omitted unless it is less than the default value set by rounds.
seed-width the width of the cells for seeds. Plain numbers are assumed to be in px units (e.g., 25 or 25px 2em for 2em or auto for automatic width sizing)
team-width the width of the cells for team names. Plain numbers are assumed to be in px units (e.g., 200 for 200px or 15em for 15em or auto for automatic width sizing)
score-width the width of the cells for scores. Plain numbers are assumed to be in px units (e.g., 25 or 25px 2em for 2em or auto for automatic width sizing)
compact set to yes for compact bracket and to disable groups.
seeds set to no to omit seed cells, and yes to always show seed cells
sets or legs the number of sets/legs in each round of matches (delimit with / for different numbers of sets or legs per round)
nowrap set to yes to prevent lines from wrapping
byes set to 1, 2, ... to specify the maximum round number with byes
boldwinner set to high to automatically bold the seed/team/scores for the team with the highest score, and low for the lowest score
hideomittedscores set to 1 to hide all omitted score cells. To only omit score cells from the second set or leg onwards, use 2
sepwidth set to 1 or 2 to override the default separator width
headings set to no to omit the column headings
RDn The name of round n. Defaults are "Round of m", ..., "Quarterfinals", "Semifinals", and "Finals", where m is the number of teams in the round.
RDn-groupm The name of group m in round n. For each round, every set of four teams is classified as a group.
RDn-seedm The seed of team m in round n. For round 1, this value defaults to the conventional seed allocation for tournaments. If omitted, the cell is hidden. To hide seeds for round 1, the value must be explicitly set to be empty. m is the zero-padded position.
RDn-teamm The name of team m in round n.
RDn-scorem The score of team m in round n.
RDn-scorem-s The score of team m in round n and set s (or leg for multileg matches). Alternatively, the last score in the round can be specified using the -agg suffix.
RD1-omit Selectively omit teams from the first round. For example, use 1 / 2 / 5 / 6 to omit team1, team2, team5, and team6 from the first round.
RD-shade Background color for the RDn headings
RDn-shadem-s Background coloring for team m in round n and set s (or leg for multileg matches).
RDn-RDn+1-path Set to 0 to omit the path between round n and round n+1.
float Float the bracket to the left or right or center or centre of the page.
clear Set to no to prevent "clearing" the left/right side of the page before floating to the left/right. This parameter is only valid with |float=left or |float=right.
aggregate Set to sets or legs to sum the sets/legs won and append to the end of the scores. Set to score to sum the scores from each set/leg and append to the end of the scores. Set to y or yes to enable the -agg suffix, but without any auto computation. Only works when there are two or more legs/sets in the round. Any automatically computed value can be overridden by setting the value manually with the -agg suffix for the score.

--
-- This module will implement {{TeamBracket}}
--
 
local p = {}
local args
local rounds
local padding

local function getArgs(frame)
	local parent = frame:getParent();
	local args = parent.args;
	for k,v in pairs(frame.args) do 
		args[k] = v
	end
	return args;
end

function getSeeds()
	local seeds = {1, 2}
	local count = 2
	local before = false
	for r = 2, rounds do
		local max = math.pow(2, r)
		for i = 1, count do
			local pos = i * 2
			if before then pos = pos - 1 end
			table.insert(seeds, pos, max - seeds[i * 2 - 1] + 1)
			before = not before
		end
		count = count * 2
	end
	return seeds
end

function addTableRow(tbl)
	return tbl:tag('tr')
end

function addBlank(row, width)
	local cell = row:tag('td')
		:css('border-width', '0')
		:css('border-style', 'solid')
		:css('border-color', 'black')
	if width then
		cell:css('width', width)
	end
	return cell
end

function addPath(rows, index, round, top, left)
	local prop = top and 'border-bottom-width' or 'border-top-width'
	if left and round == 1 then
		addBlank(rows[index]):css('height', '7px')
		addBlank(rows[index + 1]):css('height', '7px')
		return nil
	else
		local cell = addBlank(rows[index]):attr('rowspan', '2')
		if left or round < rounds and not left then
			cell:css(prop, '2px')
		end
		return cell
	end
end

function getWidth(param, default)
	local arg = args[param .. '-width']
	if not arg or string.len(arg) == 0 then
		arg = default
	end
	if tonumber(arg) ~= nil then
		arg = arg .. 'px'
	end
	return arg
end

function getTeamArg(round, type, team)
	return args[getTeamArgName(round, type, team)]
end

function getTeamArgName(round, type, team)
	return string.format('RD%d-%s' .. padding, round, type, team)
end

function getRoundName(round)
	local name = args['RD' .. round]
	if name and string.len(name) > 0 then
		return name
	end
	local roundFromLast = rounds - round + 1
	if roundFromLast == 1 then
		return "Finals"
	elseif roundFromLast == 2 then
		return "Semifinals"
	elseif roundFromLast == 3 then
		return "Quarterfinals"
	else
		return "Round of " .. math.pow(2, roundFromLast)
	end
end
 
function renderTeam(row, round, team)
	local seedArg = getTeamArg(round, 'seed', team)
	-- seed value for the paired team
	local pairSeedArg = getTeamArg(round, 'seed',
		team % 2 == 0 and team - 1 or team + 1)
	-- show seed if seed is defined for either or both
	local showSeed = seedArg and string.len(seedArg) > 0
		or pairSeedArg and string.len(pairSeedArg) > 0
	if showSeed then
		row:tag('td')
			:attr('rowspan', '2')
			:css('text-align', 'center')
			:css('background-color', '#f2f2f2')
			:css('border', '1px solid #aaa')
			:wikitext(seedArg)
			:newline()
	end
	local teamArg = getTeamArg(round, 'team', team)
	if not teamArg or string.len(teamArg) == 0 then
		teamArg = '&nbsp;'
	end
	local teamCell = row:tag('td')
		:attr('rowspan', '2')
		:css('background-color', '#f9f9f9')
		:css('border', '1px solid #aaa')
		:css('padding', '0 2px')
		:wikitext(teamArg)
		:newline()
	if not showSeed then
		teamCell:attr('colspan', '2')
	end
	row:tag('td')
		:attr('rowspan', '2')
		:css('text-align', 'center')
		:css('border', '1px solid #aaa')
		:css('background-color', '#f9f9f9')
		:wikitext(getTeamArg(round, 'score', team))
		:newline()
end

function renderRound(rows, count, r)	
	local teams = math.pow(2, rounds - r + 1)
	local step = count / teams
	local top = true
	local open = false
	local team = 1
	for i = 1, count, step do
		local offset, height, blank
		-- leave room for groups for teams other than first and last
		if team == 1 or team == teams then
			offset = top and i or i + 2
			height = step - 2
		else
			offset = top and i + 1 or i + 2
			height = step - 3
		end
		if height > 0 then
			blank = addBlank(rows[offset])
				:attr('colspan', '5')
				:attr('rowspan', height)
		end
		-- add bracket
		local j = top and i + step - 2 or i
		addPath(rows, j, r, top, true)
		renderTeam(rows[j], r, team)
		local right = addPath(rows, j, r, top, false)
		if not top then open = not open end
		if open and r < rounds then
			if blank then blank:css('border-right-width', '2px') end
			right:css('border-right-width', '2px')
		end
		team = team + 1
		top = not top
	end
end	

function renderGroups(rows, count, round)
	local roundFromLast = rounds - round + 1
	local groups = math.pow(2, roundFromLast - 2)
	local step = count / groups
	local group = 1
	for i = step / 2, count, step do
		local name = 'RD' .. round .. '-group' .. group
		addBlank(rows[i]):css('height', '7px')
		addBlank(rows[i + 1]):css('height', '7px')
		addBlank(rows[i])
			:attr('rowspan', '2')
			:attr('colspan', 5 * round - 1)
			:css('text-align', 'center')
			:css('border-right-width', '2px')
			:wikitext(args[name])
			:newline()
		group = group + 1
	end
end

function renderTree(tbl)
	-- create 3 rows for every team
	local count = math.pow(2, rounds) * 3
	local rows = {}
	for i = 1, count do
		rows[i] = addTableRow(tbl)
	end
	-- fill rows with groups
	for r = 1, rounds - 1 do
		renderGroups(rows, count, r)
	end
	-- fill rows with bracket
	for r = 1, rounds do
		renderRound(rows, count, r)
	end
end

function renderHeading(tbl)
	local titleRow = addTableRow(tbl)
	local widthRow = addTableRow(tbl)
	for r = 1, rounds do
		addBlank(titleRow)
		addBlank(widthRow, r > 1 and '5px' or nil)
		titleRow:tag('td')
			:attr('colspan', '3')
			:css('text-align', 'center')
			:css('border', '1px solid #aaa')
			:css('background-color', '#f2f2f2')
			:wikitext(getRoundName(r))
			:newline()
		addBlank(widthRow, getWidth('seed', '25px')):wikitext('&nbsp;')
		addBlank(widthRow, getWidth('team', '150px')):wikitext('&nbsp;')
		addBlank(widthRow, getWidth('score', '25px')):wikitext('&nbsp;')
		addBlank(titleRow)
		addBlank(widthRow, r < rounds and '5px' or nil)
	end
end

function p.teamBracket(frame)
	args = getArgs(frame)
	rounds = tonumber(args.rounds) or 2
	local teams = math.pow(2, rounds)
	padding = '%0' .. (teams < 10 and 1 or 2) .. 'd'
	
	-- set default seeds for round 1
	local seeds = getSeeds()
	local argname;
	for i = 1, table.getn(seeds) do
		argname = getTeamArgName(1, 'seed', i)
		if not args[argname] then
			args[argname] = seeds[i]
		end
	end

	local tbl = mw.html.create('table')
		:css('border-style', 'none')
		:css('font-size', '90%')
		:css('margin', '1em 2em 1em 1em')
		:css('border-collapse', 'separate')
		:css('border-spacing', '0')
 
	renderHeading(tbl)
	renderTree(tbl)
	return tostring(tbl)
end
 
return p