Add Custom Header
Last updated Oct 19, 2026 Prerequisite
This guide builds on top of the Build Your Own UI. It is recommended to read that guide first. Portions of the code will not be repeated here for brevity.
For a complete list of available UI Kit components, refer to the Component Library.
In this guide, we will learn how to add a custom header for your RealtimeKit meeting experience.
WebMobile
ReactWeb ComponentsAngular
RealtimeKit UI provides the RtkHeader component for a default header.
If you need additional controls, replace RtkHeader with individual UI Kit components and custom elements.
Import the required components:
import {
RtkLogo,
RtkRecordingIndicator,
RtkLivestreamIndicator,
RtkMeetingTitle,
RtkGridPagination,
RtkParticipantCount,
RtkViewerCount,
RtkClock,
} from "@cloudflare/realtimekit-ui";
In your RtkUIProvider from Build Your Own UI, replace:
<RtkHeader />
with:
<div
style={{
display: "flex",
justifyContent: "space-between",
backgroundColor: "black",
color: "white",
}}
>
<div
id="header-left"
style={{ display: "flex", alignItems: "center", height: "48px" }}
>
<RtkLogo />
<RtkRecordingIndicator />
<RtkLivestreamIndicator />
</div>
<div
id="header-center"
style={{ display: "flex", alignItems: "center", height: "48px" }}
>
<RtkMeetingTitle />
</div>
<div
id="header-right"
style={{ display: "flex", alignItems: "center", height: "48px" }}
>
<RtkGridPagination />
<RtkParticipantCount />
<RtkViewerCount />
<RtkClock />
<button onClick={handleReportBugClick}>Report Bug</button>
</div>
</div>
Define the click handler:
const handleReportBugClick = () => {
console.log("Report Bug Clicked");
};
For a complete example, refer to the custom UI example ↗︎ and the custom header component ↗︎.
RealtimeKit UI provides the rtk-header component for a default header.
If you need additional controls, replace rtk-header with individual UI Kit components and custom elements. In the renderJoinedScreen function from Build Your Own UI, replace:
<rtk-header style="display: flex; justify-content: space-between;"></rtk-header>
with:
<div
style="display: flex; justify-content: space-between; align-items: center; height: 48px; padding: 0 12px; background-color: black; color: white;"
>
<div style="display: flex; align-items: center; gap: 8px;">
<rtk-logo></rtk-logo>
<rtk-recording-indicator></rtk-recording-indicator>
<rtk-livestream-indicator></rtk-livestream-indicator>
</div>
<div style="display: flex; align-items: center;">
<rtk-meeting-title></rtk-meeting-title>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<rtk-grid-pagination></rtk-grid-pagination>
<rtk-participant-count></rtk-participant-count>
<rtk-viewer-count></rtk-viewer-count>
<rtk-clock></rtk-clock>
<button id="report-bug-button" type="button">Report Bug</button>
</div>
</div>
Register the click handler after rendering:
document.querySelector("#report-bug-button").addEventListener("click", () => {
console.log("Report Bug Clicked");
});
For a complete example, refer to the custom UI example ↗︎ and the custom header component ↗︎.
RealtimeKit UI provides the rtk-header component for a default header.
If you need additional controls, replace rtk-header with individual UI Kit components and custom elements. Create a custom header component that uses the RealtimeKit angular components directly.
Create Custom Header Component
custom-header.component.tstypescript
import { Component, AfterViewInit } from "@angular/core";
@Component({
selector: "app-custom-header",
template: `
<div class="custom-header">
<div class="header-left">
<rtk-logo></rtk-logo>
<rtk-recording-indicator></rtk-recording-indicator>
<rtk-livestream-indicator></rtk-livestream-indicator>
</div>
<div class="header-center">
<rtk-meeting-title></rtk-meeting-title>
</div>
<div class="header-right">
<rtk-grid-pagination></rtk-grid-pagination>
<rtk-participant-count></rtk-participant-count>
<rtk-viewer-count></rtk-viewer-count>
<rtk-clock></rtk-clock>
<button
type="button"
class="report-bug-button"
(click)="onReportBugClick()"
>
Report Bug
</button>
</div>
</div>
`,
styles: [
`
.custom-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 48px;
padding: 0 12px;
background-color: black;
color: white;
}
.header-left,
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.header-center {
display: flex;
align-items: center;
}
.report-bug-button {
background: none;
border: 1px solid white;
color: white;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.report-bug-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
`,
],
})
export class CustomHeaderComponent implements AfterViewInit {
ngAfterViewInit() {
console.log("Custom header initialized");
}
onReportBugClick() {
console.log("Report Bug Clicked");
// Add your custom logic here
// For example: open a modal, navigate to a form, etc.
}
}
Use in Your Meeting Component
In your main meeting component template, replace:
<rtk-header style="display: flex; justify-content: space-between;"></rtk-header>
with:
<app-custom-header></app-custom-header>
Complete Meeting Component Example
meeting.component.tstypescript
import {
Component,
ElementRef,
OnInit,
OnDestroy,
ViewChild,
} from "@angular/core";
@Component({
selector: "app-meeting",
template: `
<rtk-meeting #meetingComponent id="meeting-component">
<!-- Custom header replaces rtk-header -->
<app-custom-header></app-custom-header>
<!-- Other meeting UI components -->
<rtk-grid></rtk-grid>
<rtk-sidebar></rtk-sidebar>
</rtk-meeting>
`,
})
export class MeetingComponent implements OnInit, OnDestroy {
@ViewChild("meetingComponent", { static: true }) meetingElement!: ElementRef;
meeting: any;
private authToken = "<participant_auth_token>";
async ngOnInit() {
const RealtimeKitClient =
await import("https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit@latest/dist/index.es.js");
this.meeting = await RealtimeKitClient.default.init({
authToken: this.authToken,
});
const meetingComponent = this.meetingElement.nativeElement;
meetingComponent.showSetupScreen = true;
meetingComponent.meeting = this.meeting;
}
ngOnDestroy() {
// Cleanup logic
}
}
Module Configuration
Don't forget to declare your custom header component in your Angular module:
app.module.tstypescript
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { AppComponent } from "./app.component";
import { MeetingComponent } from "./meeting.component";
import { CustomHeaderComponent } from "./custom-header.component";
@NgModule({
declarations: [AppComponent, MeetingComponent, CustomHeaderComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA], // Required for RTK web components
})
export class AppModule {}
This approach gives you full control over the header layout while maintaining Angular's component architecture and event handling patterns.
RealtimeKit UI provides the RtkMeetingHeaderView component for a default header.
If you need additional controls, replace RtkMeetingHeaderView with individual UI Kit components and custom elements.
Import the required components:
import RealtimeKitUI
Create custom header view
Create a custom header view that uses the RealtimeKit iOS components directly:
CustomHeaderView.swiftswift
import UIKit
import RealtimeKit
import RealtimeKitUI
class CustomHeaderView: UIView {
private let meeting: RealtimeKitClient
private lazy var titleLabel: RtkMeetingTitleLabel = {
return RtkMeetingTitleLabel(meeting: meeting)
}()
private lazy var participantCountView: RtkParticipantCountView = {
return RtkParticipantCountView(meeting: meeting)
}()
private lazy var clockView: RtkClockView = {
return RtkClockView(meeting: meeting)
}()
private lazy var recordingView: RtkRecordingView = {
return RtkRecordingView(meeting: meeting)
}()
private let reportBugButton = UIButton(type: .system)
init(meeting: RealtimeKitClient) {
self.meeting = meeting
super.init(frame: .zero)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .black
// Configure report bug button
reportBugButton.setTitle("Report Bug", for: .normal)
reportBugButton.setTitleColor(.white, for: .normal)
reportBugButton.layer.borderColor = UIColor.white.cgColor
reportBugButton.layer.borderWidth = 1
reportBugButton.layer.cornerRadius = 4
reportBugButton.contentEdgeInsets = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
reportBugButton.addTarget(self, action: #selector(handleReportBugTap), for: .touchUpInside)
// Create stack views for layout
let leftStack = UIStackView(arrangedSubviews: [recordingView])
leftStack.axis = .horizontal
leftStack.spacing = 8
leftStack.alignment = .center
let centerStack = UIStackView(arrangedSubviews: [titleLabel])
centerStack.axis = .horizontal
centerStack.alignment = .center
let rightStack = UIStackView(arrangedSubviews: [
participantCountView,
clockView,
reportBugButton
])
rightStack.axis = .horizontal
rightStack.spacing = 8
rightStack.alignment = .center
let mainStack = UIStackView(arrangedSubviews: [leftStack, centerStack, rightStack])
mainStack.axis = .horizontal
mainStack.distribution = .equalSpacing
mainStack.alignment = .center
mainStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(mainStack)
NSLayoutConstraint.activate([
mainStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
mainStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
mainStack.topAnchor.constraint(equalTo: topAnchor),
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor),
heightAnchor.constraint(equalToConstant: 48)
])
}
@objc private func handleReportBugTap() {
print("Report Bug Tapped")
// Add your custom logic here
}
}
Use in your meeting view controller
In your MeetingViewController, replace the default header with your custom header:
MeetingViewController.swiftswift
import UIKit
import RealtimeKit
import RealtimeKitUI
class MeetingViewController: UIViewController {
private var meeting: RealtimeKitClient?
private var customHeader: CustomHeaderView?
override func viewDidLoad() {
super.viewDidLoad()
setupMeeting()
}
private func setupMeeting() {
Task {
do {
let client = try await RealtimeKitClient.init(authToken: "<PARTICIPANT_AUTH_TOKEN>")
self.meeting = client
await MainActor.run {
setupCustomHeader(meeting: client)
}
} catch {
print("Failed to initialize meeting: \(error)")
}
}
}
private func setupCustomHeader(meeting: RealtimeKitClient) {
let header = CustomHeaderView(meeting: meeting)
self.customHeader = header
view.addSubview(header)
header.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
header.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
header.leadingAnchor.constraint(equalTo: view.leadingAnchor),
header.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
