Skip to main content
Version: V3

Layout Templates

Themes and templates

A theme sets colors, the logo, and other visual options. A layout template changes page structure. A theme can use one layout template (chosen on the theme's Advanced tab), and several themes can share the same template. With no template assigned, the panel uses its built-in layout.

Creating a template

  1. Go to Settings → Layout Templates and create a template.
  2. Add one or more surfaces (for example LoginPage or Shell) and edit each surface's markup and CSS. Use Validate / Preview to check a surface as you work.
  3. Save the template, then open a theme's Advanced tab and choose it. Select that theme to see it in action.
Locked out by a template?

Add ?safeMode=1 to the panel's address to temporarily ignore the template's markup and CSS and fall back to the built-in layout — handy if a custom login or shell ever renders incorrectly.

How a surface is written

A template is organized into surfaces — named regions you can take over (the login page, the whole shell, individual parts of the shell, or the pages of a game or Docker server). In a surface you write ordinary HTML with three special tags:

  • {{ Variable }} — inserts a value, such as the application name or the signed-in user.
  • {% component "name" %} — drops in a live, interactive piece of the panel (the login form, the navigation menu, the dark-mode toggle, and so on).
  • {% surface "Key" %} — places one of the customizable regions inside your shell.

Each surface also has its own CSS box, and the template has a base stylesheet that applies whenever the template is active. The editor's Reference tab lists every available variable, component, and surface key.

note

Templates imported from others run in a safe mode that strips scripts, so a shared template can't run code. Some advanced CSS (for example blur or animations) is also filtered unless an administrator marks the theme as trusted.

Variables

Insert a variable with {{ AppName }}, or the dotted form for nested values such as {{ Branding.Title }}. Output is automatically HTML-encoded.

VariableTypeDescription
AppNamestringThe application name.
AppVersionstringThe running version.
CulturestringThe current language code.
IsAuthenticatedbooleanWhether the visitor is signed in.
UserNamestringThe signed-in user's name.
Branding.TitlestringThe branding title.
Branding.SubtitlestringThe branding subtitle.
Branding.LogoUrlstringThe branding logo image URL.

Components

Add a component with {% component "name" %}. The most useful ones:

ComponentWhat it adds
login-formThe sign-in form (required somewhere on the login page).
brandThe logo and branding block.
shellThe entire built-in shell — a quick base when you only want to restyle with CSS.
bodyThe current page's content (required somewhere in a custom shell).
menuThe navigation drawer and menu.
menu-toggleThe button that opens and collapses the drawer.
topbarThe complete built-in top bar.
breadcrumbsThe breadcrumb trail.
dark-mode-toggleThe light / dark switch.
language-selectorThe language picker.
connection-statusA warning shown only when the live connection drops.
connection-indicatorAn always-on connection dot (green / amber / red).
health-indicatorA system-health indicator (shown only to permitted users).
announcementsThe customer announcements control.
footer-resourcesThe current page's live stats — a game or Docker server's disk, CPU, memory, network and players.

The surfaces

Login page

The LoginPage surface replaces the entire login page. It must include the login-form component, or no one could sign in.

<div class="login-glass-card">
<h2>🎨 My Custom Login Template</h2>
<p>{{ Branding.Title }} — v{{ AppVersion }}</p>
{% component "brand" %}
{% component "login-form" %}
{% component "language-selector" %}
</div>

App shell

The Shell surface replaces the shell that wraps every page. It must include either the body component (the page content) or the shell component (the entire built-in shell). The example below rebuilds the shell from individual pieces — a custom top bar, the menu, the page body, and the footer region — and places the top-bar and footer surfaces inside it.

When you hand-build the shell like this, one CSS rule is required so the navigation drawer sits correctly below your top bar — it's the first rule in the CSS tab below.

<div class="tca-shell-header">
{% component "menu-toggle" %}{% surface "Shell.TopBarStart" %}
<span class="tca-shell-title">{{ AppName }}</span>
{% component "breadcrumbs" %}
<span class="tca-shell-spacer"></span>
{% component "connection-status" %}{% component "dark-mode-toggle" %}{% surface "Shell.TopBarEnd" %}
</div>
{% component "menu" %}
<div class="tca-shell-body">
{% component "body" %}
</div>
{% surface "Shell.Footer" %}
Just restyling?

If you only want to recolor or restyle the panel without rebuilding it, make your Shell simply {% component "shell" %} and put everything in the stylesheet — you won't need the drawer rule above.

And if you don't need to change the shell's markup or CSS at all, just disable the Shell surface (or leave it out) — the built-in shell is used, and the other surfaces (top bar, drawer, footer and content slots) still appear on their own.

Top bar slots

The Shell.TopBarStart and Shell.TopBarEnd surfaces add extra content at the start (left) or end (right) of the top app bar, alongside the built-in toggles. Both appear automatically in the built-in top bar; in a hand-built Shell you position them with {% surface "Shell.TopBarStart" %} / {% surface "Shell.TopBarEnd" %}, as shown in the shell example above.

<!-- Shell.TopBarStart -->
<div class="tca-topbar-start">
<span class="tca-topbar-start-icon">&#9889;</span>
<span>Status: Online</span>
</div>
<!-- Shell.TopBarEnd -->
<div class="tca-topbar-end">
<span class="tca-topbar-end-icon">&#9733;</span>
<span>Pro Plan</span>
</div>

Shell.DrawerHeader and Shell.DrawerFooter add content above or below the navigation menu inside the drawer — for example a brand zone at the top, or an account and sign-out block at the bottom.

<!-- Shell.DrawerHeader -->
<div class="tca-drawer-header">
<div class="tca-drawer-header-logo">&#9889;</div>
<div>
<div class="tca-drawer-header-name">{{ AppName }}</div>
<div class="tca-drawer-header-sub">{{ Branding.Subtitle }}</div>
</div>
</div>
<!-- Shell.DrawerFooter -->
<div class="tca-drawer-footer">
<span class="tca-drawer-footer-dot"></span>
<span class="tca-drawer-footer-text">Signed in as <strong>{{ UserName }}</strong></span>
</div>

Before and after content

Shell.BeforeContent and Shell.AfterContent are banner strips shown immediately above or below the page content, on every page.

<!-- Shell.BeforeContent -->
<div class="tca-before-content">
<span class="tca-before-content-icon">&#8505;</span>
<span>Scheduled maintenance this Sunday at 02:00 UTC.</span>
</div>
<!-- Shell.AfterContent -->
<div class="tca-after-content">
&copy; {{ AppName }} — all rights reserved.
</div>

The Shell.Footer surface replaces the contents of the footer bar. If you don't define it, the built-in footer is shown.

<div class="tcf">
{% component "health-indicator" %}
<a class="tcf-link" href="/support">Support</a>
<a class="tcf-link" href="https://status.example.com" target="_blank" rel="noopener">Status</a>
<span class="tcf-grow"></span>
<span class="tcf-brand">&copy; 2026 {{ Branding.Title }}</span>
<span class="tcf-ver">v{{ AppVersion }}</span>
{% component "connection-indicator" %}
</div>
tip

Add {% component "footer-resources" %} to a custom footer to show the current page's live stats — a game or Docker server's disk, CPU, memory, network and players.

The Shell.MenuLinks surface adds your own links to the end of the navigation menu, styled to match the built-in items. To look native, each link uses the same markup as the built-in items: an <a class="mud-nav-link"> containing a Font Awesome icon and a text label.

<a href="https://docs.tcadmin.com" target="_blank" rel="noopener noreferrer" class="mud-nav-link mud-ripple">
<i class="fa-solid fa-book mud-icon-root mud-nav-link-icon mud-nav-link-icon-default" style="font-size:24px;width:24px;text-align:center;"></i>
<div class="mud-nav-link-text">Documentation</div>
</a>
<a href="https://help.tcadmin.com" target="_blank" rel="noopener noreferrer" class="mud-nav-link mud-ripple">
<i class="fa-solid fa-life-ring mud-icon-root mud-nav-link-icon mud-nav-link-icon-default" style="font-size:24px;width:24px;text-align:center;"></i>
<div class="mud-nav-link-text">Support</div>
</a>
note

Use Font Awesome icons (<i class="fa-solid ...">). Keep the mud-icon-root class so the label collapses to an icon-only button when the drawer is minimized.

Service pages

Layout templates can also restyle the pages of an individual game or Docker server. Four surfaces cover them:

SurfaceWhat it replaces
GameService.HomeA game server's home page — the content you see when you open the service.
DockerService.HomeA Docker server's home page.
GameService.HeaderA game server's header strip — the title, connection cards and power buttons shown on every page of the service.
DockerService.HeaderA Docker server's header strip, on every page of the service.

A Home surface replaces only the page's content — the panel's top bar, menu and bottom bar stay. A Header surface replaces the band that stays visible while you move between a service's pages (Home, Console, File Manager and so on). Voice servers use the GameService surfaces. These surfaces appear on the service's pages automatically — they are never placed inside another surface with {% surface %} — and the editor's Validate / Preview shows them with a sample service and every permission granted.

If a template doesn't define a service surface, the built-in page is shown — an unfinished template never leaves a service unusable.

Hiding the built-in header

A defined surface always wins — so to remove the header strip entirely, add a Header surface containing just an empty <div></div>. It renders nothing, and the built-in header stays hidden.

Targeting one game or blueprint

On their own, the service surfaces apply to every game or Docker server. To design a page for one specific game or Docker blueprint, add its numeric id to the end of the surface key — GameService.Home.526 applies only to servers of game 526 and wins over the general GameService.Home. When you add a surface, the editor's Limit to blueprint picker composes the key for you (typing the suffix by hand works too). If the specific surface is disabled, those servers fall back to the general one.

Service variables

Unlike the other surfaces, a service surface is rendered for the service and the person viewing it each time the page opens. Three extra variable groups are available alongside the standard ones:

  • Service.* — details of the service being viewed.
  • Permissions.* — whether the viewer can see each feature of this service, matching the built-in page's rules exactly.
  • Features.* — which features the game or blueprint has switched on at all, regardless of the viewer's permissions.

Use them like any other variable — {{ Service.Name }}, or in conditions and loops:

{% if Permissions.Console %}<div class="my-console">{% component "game-console" %}</div>{% endif %}

{% for p in Service.Ports %}{{ p.Name }}: {{ p.Port }}<br>{% endfor %}
Snapshots, not live values

Variable values are captured when the page loads. For numbers that update in real time — status, players, CPU, memory — place the service components instead.

VariableTypeDescription
Service.ServiceIdnumberThe service's id.
Service.NamestringThe service's name.
Service.ServiceTypestringGameService (also voice servers) or DockerService.
Service.BlueprintIdnumberThe game or Docker blueprint id.
Service.BlueprintNamestringThe game or Docker blueprint name.
Service.IpAddressstringThe service's IP address.
Service.HostnamestringThe service's hostname, when one is set.
Service.HostnameOrIpstringThe hostname if set, otherwise the IP address.
Service.UrlstringThe relative address of the service's home page — handy for links to sub-pages, e.g. {{ Service.Url }}/FileManager.
Service.StatusstringThe status name when the page loaded (for example Started, Stopped).
Service.OwnerNamestringThe name of the user who owns the service.
Service.DiskSpacenumberThe disk quota in bytes (empty when unlimited).
Service.DiskUsagenumberThe last known disk usage in bytes.
Service.SlotsnumberPlayer slots (game servers).
Service.GamePortnumberThe game port (game servers).
Service.QueryPortnumberThe query port (game servers).
Service.RconPortnumberThe RCON port (game servers).
Service.VirtualServerNamestringThe virtual server hosting the service, when there is one.
Service.CpuLimitnumberThe CPU limit, as a percentage.
Service.MemoryLimitnumberThe memory limit in bytes.
Service.ImagestringThe container image (Docker servers).
Service.TagstringThe image tag (Docker servers).
Service.PortslistEvery named port — loop with {% for p in Service.Ports %}.

Each entry in Service.Ports has:

VariableTypeDescription
NamestringThe port's name (for example GamePort).
PortnumberThe port number.
DescriptionstringThe port's description, when one is set.
UristringA click-to-connect link when the game defines one (for example steam://connect/…).

The Permissions.* values mirror what the built-in pages would show this viewer for this service — the stat values already include LiveStats, and Console and FastDL already include the matching feature toggle, so each one can be tested on its own. They control visibility only; every action is still checked when it's used.

VariableTypeDescription
Permissions.ControlbooleanStart, stop and restart the service.
Permissions.KillServicebooleanForce-stop the service.
Permissions.ConsolebooleanUse the console (game) or the interactive terminal (Docker).
Permissions.LogsbooleanView the service's log files.
Permissions.ServiceActivitybooleanView the service's activity history.
Permissions.LiveStatsbooleanSee live usage statistics.
Permissions.PlayerStatsbooleanSee the players tile.
Permissions.CpuStatsbooleanSee the CPU tile.
Permissions.MemoryStatsbooleanSee the memory tile.
Permissions.NetworkStatsbooleanSee the network tile.
Permissions.ServiceSettingsbooleanOpen the service's settings.
Permissions.CustomScriptsbooleanManage the service's custom scripts.
Permissions.ReinstallbooleanReinstall the service.
Permissions.DeletebooleanDelete the service.
Permissions.ModsbooleanInstall mods.
Permissions.FastDLbooleanManage Fast Downloads.
Permissions.LogConsolebooleanView the container log (Docker servers; always false for game servers).
Permissions.FileManagerbooleanUse the File Manager.
Permissions.ConfigFilesbooleanEdit configuration files.
Permissions.ScheduledTasksbooleanManage scheduled tasks.
Permissions.BackupsbooleanManage backups.
Permissions.FtpbooleanSee FTP connection details.

The Features.* values tell you what the game or blueprint has enabled at all — useful to tell "this viewer can't see it" apart from "this service doesn't have it". All are booleans: Features.ConsoleEnabled, Features.FileManagerEnabled, Features.ConfigFilesEnabled, Features.BackupsEnabled, Features.ScheduledTasksEnabled, Features.UpdatesEnabled, Features.SteamUpdateEnabled and Features.FastDLEnabled.

Service components

These components drop live, interactive pieces of the service pages into your surface. Each one shows and hides itself using the same rules as the built-in page — a viewer without the right permission simply sees nothing — so wrap one in {% if Permissions.… %} only when your surrounding markup should disappear too. The service-… components work on both service types; game-… and docker-… components render only for their own type. Outside a service's pages (for example in a Shell surface) they render nothing.

ComponentWhat it adds
service-titleThe service's name, icon and address line (updates live).
service-powerThe Start / Stop / Restart / Kill buttons.
service-advanced-actionsThe extra action buttons — settings, scripts, reinstall, delete, move.
service-detailsThe service details table.
service-uptime-tileThe uptime tile.
service-cpu-tileThe live CPU tile.
service-memory-tileThe live memory tile.
service-disk-tileThe disk usage tile.
service-recent-logsThe recent log files panel.
service-recent-activityThe recent activity timeline.
game-status-alertThe warning shown while a game server's status is unknown.
game-connection-infoThe connection, FTPS and RCON info cards.
game-players-tileThe live players tile.
game-network-tileThe live network traffic tile.
game-consoleThe game's web console (text or screen capture).
docker-status-headerThe container status header with uptime, ports and quick settings.
docker-portsThe IP and port chips.
docker-logs-terminalThe read-only container log terminal.
docker-exec-terminalThe interactive container terminal.

A console or terminal placed in a surface connects as soon as the page opens (the built-in pages wait until you open their tab).

Consoles need a fixed height

In your surface CSS, always give game-console and the Docker terminals a fixed-height container (for example height: 480px; min-width: 0; overflow: hidden;), and use minmax(0, …) for any CSS grid tracks around them. In an auto-sized cell, the terminal and the cell resize each other in a loop and the browser tab can freeze.

Responsive layouts

Media queries are filtered out of shared templates' CSS, but the panel's built-in helper classes still work: add class="d-none d-md-flex" to an element to hide it on phones (it appears from medium screens up).

Example: a game home page

Saved as GameService.Home — a hero strip with the power buttons, a row of live tiles, the console, and the standard info blocks:

<div class="svc-hero">
<div>
<h2>{{ Service.Name }}</h2>
<p>{{ Service.BlueprintName }} — {{ Service.HostnameOrIp }}{% if Service.GamePort %}:{{ Service.GamePort }}{% endif %}</p>
</div>
<span class="svc-hero-spacer"></span>
{% component "service-power" %}
</div>
{% component "game-status-alert" %}
<div class="svc-tiles">
{% component "service-uptime-tile" %}
{% component "game-players-tile" %}
{% component "service-cpu-tile" %}
{% component "service-memory-tile" %}
</div>
{% if Permissions.Console %}
<div class="svc-console">{% component "game-console" %}</div>
{% endif %}
{% component "game-connection-info" %}
{% component "service-details" %}

Example: a service header

Saved as GameService.Header — replaces the title / connection / power band on every page of the service. The connection cards are hidden on phones with the d-none d-md-flex helper classes:

<div class="svc-head">
{% component "service-title" %}
<span class="svc-head-spacer"></span>
<div class="d-none d-md-flex">{% component "game-connection-info" %}</div>
{% component "service-power" %}
</div>

Sharing templates

A finished template can move between installations:

  • Export / Import — from a template's editor, Export downloads it as a file; Import on the Layout Templates list loads one back in.
  • Plugin RepositoryShare publishes a template to a connected plugin repository, where other installations can browse and install it. You can also share a theme together with its template as a single bundle, so the colors and the layout travel together.