Customize view and page tabs
A Browser window has two types of tabs: page tabs, which switch between pages, and view tabs, which switch between the views within a page. You can customize both so users can find what they need at a glance, even when many tabs are open.
For page tabs and view tabs, you can set minimum and maximum tab widths (HERE Core 42 or later).
For view tabs, in HERE Core UI 45 or later, you can also:
-
Override or hide the favicon on individual view tabs.
-
Set the background color and title text color of individual view tabs, and change them at runtime.
-
Add one or more interactive icon buttons to a view tab, and run your own code when a user clicks them.
-
Let view tabs resize to fit their content, within the minimum and maximum widths you set.
-
Look up the page and window that contain a view with a single call.
In HERE Core UI 45 or later, Browser also hides the close button on inactive view tabs until the user hovers over the tab or moves keyboard focus to it, which leaves more room for the tab title.
These settings are part of the view and window options you already use: createView, updateOptions, and snapshots. You don't need to modify the tab DOM, write preload scripts, or inject CSS.
To pin or lock page tabs, see Pin and lock page tabs.
Summary
| Customization | Applies to | Where you set it | Can change at runtime |
|---|---|---|---|
| Tab width | Page tabs and view tabs | workspacePlatform.pageTabDimensions and workspacePlatform.viewTabDimensions on the window options | No |
| Content-based width | View tabs | workspacePlatform.viewTabDimensions.widthMode on the window options | No |
| Favicon override or hide | View tabs | icon on the view options | Yes, with view.updateOptions() |
| Background and text color | View tabs | workspacePlatform.viewTab.backgroundColor and workspacePlatform.viewTab.fontColor on the view options | Yes, with view.updateOptions() |
| Interactive controls | View tabs | workspacePlatform.viewTab.controls on the view options | Yes, with view.updateOptions() |
| Page and window lookup | View tabs | platform.Browser.getPageByViewIdentity() | Not applicable |
Customize view and page tab width
In HERE Core 42 or later, Browser can resize page tabs and view tabs automatically, making them wider or narrower within limits you set. This behavior mirrors the behavior found in Chrome. You set the limits separately for each type of tab:
| Property | Applies to |
|---|---|
pageTabDimensions | Page tabs |
viewTabDimensions | View tabs |
Each property takes a minWidth and a maxWidth. Tabs never shrink below minWidth or grow beyond maxWidth. All tabs of the same type have the same width, unless you set view tabs to size to their content.
You can specify sizes in pixels (for example, "100px") or as a percentage of the tab strip width (for example, "20%"). A minWidth below 28px is ignored, and 28px is used instead.
Both properties are optional, and you can set one without the other. If you omit a property, that type of tab uses the default width of 120px for both minWidth and maxWidth.
When there are more tabs than fit in the window:
- Page tabs keep the existing horizontal scrollbar.
- View tabs scroll horizontally, with a shadow at the edge of the tab row to show that more tabs are hidden. This replaces the dropdown overflow menu.
The close button (✕) stays available on every tab, whatever its width. For inactive view tabs in HERE Core UI 45 or later, it appears when the user hovers over the tab or focuses it. See Close button visibility.
JavaScript object example
platform: {
defaultWindowOptions: {
workspacePlatform: {
// Page tab width
pageTabDimensions: {
minWidth: "30px",
maxWidth: "400px"
},
// View tab width
viewTabDimensions: {
minWidth: "50px",
maxWidth: "400px"
}
}
}
}
Platform manifest example
"platform": {
"defaultWindowOptions": {
"workspacePlatform": {
"pageTabDimensions": {
"minWidth": "30px",
"maxWidth": "400px"
},
"viewTabDimensions": {
"minWidth": "50px",
"maxWidth": "400px"
}
}
}
}
Size view tabs to fit their content
In HERE Core UI 45 or later, you can let each view tab size itself to its content instead of giving all view tabs the same width. Set viewTabDimensions.widthMode to 'content':
widthMode value | Result |
|---|---|
'uniform' (default) | All view tabs have the same width, within minWidth and maxWidth. |
'content' | Each view tab is as wide as its title, favicon, and controls need, within minWidth and maxWidth. Tabs with short titles are narrower than tabs with long titles. Titles that don't fit within maxWidth are truncated. |
The default limits depend on the width mode. With 'uniform', both minWidth and maxWidth default to 120px. With 'content', there is no default minWidth beyond the 28px floor that applies in both modes, and maxWidth defaults to 200px.
widthMode is available for view tabs only. Page tabs always have the same width.
await platform.Browser.createWindow({
workspacePlatform: {
viewTabDimensions: {
widthMode: "content",
minWidth: "60px",
maxWidth: "200px"
}
}
});
Override or hide the view tab icon
By default, a view tab shows the favicon of the page loaded in the view, as long as favicons are enabled for the window with experimental.showFavicons. To let your platform choose the icon instead, set the icon property on the view options. The icon property takes precedence over the window-level favicon setting for that view.
icon value | Result |
|---|---|
| Omitted | Inherit the window setting. If showFavicons is enabled, the tab shows the page favicon, then the default favicon, then no icon. If showFavicons is disabled, the tab shows no icon. |
| A URL | Always show the image at that URL, even if showFavicons is disabled. |
'hide' | Always hide the icon, even if showFavicons is enabled. The tab title expands into the space the icon used. |
'unset' | Remove a previous override and inherit the window setting again. Same as omitted. |
An icon URL or 'hide' stays in effect when the page in the view later changes its own favicon.
import * as WorkspacePlatform from "@openfin/workspace-platform";
const platform = WorkspacePlatform.getCurrentSync();
// Always show your own icon on this tab
const view = await platform.createView({
url: "https://intranet.example.com/app",
icon: "https://intranet.example.com/app-mark.png"
});
// Hide the icon; the title uses the freed space
await view.updateOptions({ icon: "hide" });
// Return to the window-level favicon setting
await view.updateOptions({ icon: "unset" });
When you call updateOptions() without an icon property, the current icon doesn't change.
The view icon property controls the view tab only. It is separate from the window icon property, which sets the taskbar icon.
Set view tab colors
To color-code a view tab, set these properties in workspacePlatform.viewTab on the view options:
| Property | What it colors |
|---|---|
backgroundColor | The tab background. |
fontColor | The tab title text. It does not change the color of the close or lock icons. |
The two properties are independent, so you can set one without the other. Each takes a CSS color value for each tab state:
| State | When it applies |
|---|---|
default | The tab is inactive. |
hover | The user's pointer is over the tab, and the tab is not active. |
active | The tab is the selected tab in its stack. |
focus | The tab is active, and its view has focus. |
Every state is optional. Any state you omit uses the color from the current theme, set by the color.coreui.browserViewTab tokens described in Granular color theming. The colors apply only to that view's tab; other tabs keep the theme colors.
const view = await platform.createView({
url: "https://intranet.example.com/app",
workspacePlatform: {
viewTab: {
backgroundColor: {
default: "#0f172a",
hover: "#1e293b",
active: "#334155",
focus: "#475569"
},
fontColor: {
default: "#cbd5e1",
active: "#ffffff",
focus: "#ffffff"
}
}
}
});
To change colors at runtime, for example when your application state changes, call updateOptions(). The states you pass are merged with the existing colors, so you only need to pass the states that change:
// Only the active background changes; every other color keeps its value
await view.updateOptions({
workspacePlatform: {
viewTab: {
backgroundColor: { active: "#dc2626" }
}
}
});
Because colors are merged, leaving out a state doesn't remove its color. To return a state to the theme color, set it to an empty string:
// Return the active background to the theme color
await view.updateOptions({
workspacePlatform: {
viewTab: {
backgroundColor: { active: "" }
}
}
});
Choose background and text colors that contrast with each other in every state, in both light and dark themes.
Add interactive controls to view tabs
You can add icon buttons, called controls, to a view tab. For example, you might add a status indicator, a notification badge, or a button that opens a color-channel picker. Declare the controls in workspacePlatform.viewTab.controls on the view options, and register a custom action to run when a user clicks one.
Each control has these properties:
| Property | Required | Description |
|---|---|---|
iconUrl | No | The icon to display. Either a single URL, or an object with light and dark URLs. See Use theme-aware icons. |
tooltip | Yes | Text shown on hover. Also used as the accessible name of the control. |
action | Yes | The custom action to run on click: { id, customData? }. The id must match a key in the customActions you pass to WorkspacePlatform.init(). |
position | No | Where the control sits in the tab. Default: { relativeTo: 'title', placement: 'before' }. See Position controls. |
disabled | No | If true, the control is displayed but can't be clicked or focused. |
Each control provides a square space, 15×15 pixels, the same size as the favicon. Icons are displayed as you provide them; Browser doesn't tint them.
Controls have a fixed size. On a tab that is too narrow to fit all of its controls, the controls farthest to the right are cut off. To avoid this, set a minWidth that leaves room for your controls, or use content-based width.
Controls require the version 2 layout engine, which is the Browser default. If a window opts back in to the version 1 engine, its tabs don't show controls, and the controls option is ignored without an error.
Declare controls and handle clicks
When a user clicks a control, Browser calls the matching custom action in your platform provider. The payload has callerType set to CustomActionCallerType.ViewTabControl and includes these properties:
| Property | Description |
|---|---|
viewIdentity | The identity of the view whose tab was clicked. |
windowIdentity | The identity of the Browser window that contains the tab. |
customData | The customData from the control's action. |
x, y | The client coordinates of the click, relative to the Browser window. |
Controls don't have their own IDs. If several controls use the same action, use customData to tell them apart.
import * as WorkspacePlatform from "@openfin/workspace-platform";
await WorkspacePlatform.init({
browser: { /* ... */ },
customActions: {
"pin-view": async (payload: WorkspacePlatform.CustomActionPayload) => {
if (payload.callerType !== WorkspacePlatform.CustomActionCallerType.ViewTabControl) {
return;
}
const { viewIdentity, customData } = payload;
// Your logic, for example pin the view identified by viewIdentity
}
}
});
const platform = WorkspacePlatform.getCurrentSync();
await platform.createView({
url: "https://intranet.example.com/app",
workspacePlatform: {
viewTab: {
controls: [
{
iconUrl: "https://cdn.example.com/pin.svg",
tooltip: "Pin view",
action: { id: "pin-view", customData: { appId: "orders" } }
}
]
}
}
});
Clicking a control doesn't select or close the tab. As with the close button, pressing a control and dragging still moves the tab.
Position controls
You can place a control before or after one of two anchors in the tab: the title or the favicon. Set position.relativeTo to 'title' or 'favicon', and set position.placement to 'before' or 'after'.
The tab contents are laid out in this order:
[before favicon] [favicon] [after favicon / before title] [title] [after title] [close]
When several controls share the same position, they are displayed in the order they appear in the controls array.
The favicon anchor still exists when the favicon is hidden, so a control's position stays the same.
controls: [
{
// Immediately to the left of the title
position: { relativeTo: "title", placement: "before" },
iconUrl: "https://cdn.example.com/status.svg",
tooltip: "Status",
action: { id: "show-status" }
},
{
// Immediately to the right of the title
position: { relativeTo: "title", placement: "after" },
iconUrl: "https://cdn.example.com/alert.svg",
tooltip: "Alerts",
action: { id: "show-alerts" }
},
{
// At the leading edge of the tab, before the favicon
position: { relativeTo: "favicon", placement: "before" },
iconUrl: "https://cdn.example.com/pin.svg",
tooltip: "Pin",
action: { id: "pin-view" }
}
]
Use theme-aware icons
To display a different icon in light and dark color schemes, set iconUrl to an object with light and dark URLs. Browser switches icons when the color scheme changes. A single URL string is used in both schemes.
{
iconUrl: {
light: "https://cdn.example.com/pin-light.svg",
dark: "https://cdn.example.com/pin-dark.svg"
},
tooltip: "Pin",
action: { id: "pin-view" }
}
Update or remove controls at runtime
To add, remove, or swap controls when your application state changes, call updateOptions() with a new controls array. Unlike backgroundColor, the controls array is replaced, not merged. Pass the complete list of controls you want the tab to show.
// Replace the tab's controls with a notification badge
await view.updateOptions({
workspacePlatform: {
viewTab: {
controls: [
{
iconUrl: "https://cdn.example.com/badge-3.svg",
tooltip: "3 new notifications",
action: { id: "show-notifications" }
}
]
}
}
});
// Remove all controls
await view.updateOptions({
workspacePlatform: { viewTab: { controls: [] } }
});
Open a menu from a control
Browser doesn't provide a built-in dropdown for controls. To show a menu, color picker, or pop-up window, open it from your custom action. Use the x and y coordinates in the payload to position it next to the control.
customActions: {
"choose-channel": async (payload: WorkspacePlatform.CustomActionPayload) => {
if (payload.callerType !== WorkspacePlatform.CustomActionCallerType.ViewTabControl) {
return;
}
const browserWindow = fin.Window.wrapSync(payload.windowIdentity);
const { result, data } = await browserWindow.showPopupMenu({
template: [
{ label: "Red channel", data: "#dc2626" },
{ label: "Green channel", data: "#16a34a" },
{ label: "Blue channel", data: "#2563eb" }
],
x: payload.x,
y: payload.y
});
if (result === "clicked") {
await fin.View.wrapSync(payload.viewIdentity).updateOptions({
workspacePlatform: { viewTab: { backgroundColor: { default: data } } }
});
}
}
}
Right-click the tab title
Right-clicking a view tab opens the view tab context menu. To add your own options to that menu, override openViewTabContextMenu. See Customize Browser features.
Close button visibility
In HERE Core UI 45 or later, the close button (✕) on an inactive view tab is hidden until the user hovers over the tab or moves keyboard focus to it. The space the close button would use is available for the tab title. The active tab always displays its close button.
View tabs on locked pages keep their existing behavior: the close button is never shown. Controls you add to tabs on locked pages are still displayed and can still be clicked.
Look up the page and window for a view
To find the page and window that contain a view, call platform.Browser.getPageByViewIdentity(). It returns the window identity and the page, including page.pageId and page.title, in a single call, so you don't need to walk the layout tree.
For example, you can use it to give every view tab on the same page the same color:
const pageColors: Record<string, string> = {
"trading-page": "#1e3a8a",
"research-page": "#14532d"
};
async function colorTabByPage(viewIdentity: OpenFin.Identity) {
const { windowIdentity, page } = await platform.Browser.getPageByViewIdentity(viewIdentity);
const color = pageColors[page.pageId];
if (!color) {
return;
}
await fin.View.wrapSync(viewIdentity).updateOptions({
workspacePlatform: { viewTab: { backgroundColor: { default: color } } }
});
}
Set defaults for all views and windows
To apply the same tab customization to every view or window, set it in BrowserInitConfig when you call WorkspacePlatform.init():
defaultViewOptions.workspacePlatform.viewTabsets the default background colors, text colors, and controls for every view.defaultWindowOptions.workspacePlatform.pageTabDimensionsandviewTabDimensionsset the default page tab and view tab widths for every window.viewTabDimensionsalso sets the defaultwidthMode.
Values you pass when you create a view or window override these defaults.
await WorkspacePlatform.init({
browser: {
defaultWindowOptions: {
workspacePlatform: {
pageTabDimensions: {
minWidth: "30px",
maxWidth: "400px"
},
viewTabDimensions: {
widthMode: "content",
minWidth: "60px",
maxWidth: "200px"
}
}
},
defaultViewOptions: {
workspacePlatform: {
viewTab: {
controls: [
{
iconUrl: "https://cdn.example.com/pin.svg",
tooltip: "Pin view",
action: { id: "pin-view" }
}
]
}
}
}
},
customActions: {
"pin-view": async (payload) => { /* ... */ }
}
});
Snapshots
The icon and workspacePlatform.viewTab values are saved with the view. They are included in the result of platform.getViewSnapshot() and are restored when a snapshot is applied.
const snapshot = await platform.getViewSnapshot(view.identity);
// snapshot.icon === "hide"
// snapshot.workspacePlatform.viewTab.backgroundColor.active === "#dc2626"
Controls are saved as configuration only. Your customActions handlers are not part of the snapshot, so they must be registered in WorkspacePlatform.init() whenever the platform starts.
Accessibility
Controls are part of the existing keyboard navigation for view tabs:
-
Users reach controls with the arrow keys, in the same way as the close button. Controls are not separate Tab-key stops.
-
In the
ViewTabElementskeyboard navigation settings, controls are'active-tab-custom-control'(controls on the active tab) and'inactive-tab-custom-control'(controls on other tabs). If you provide your ownarrowNavigationlist, include these values so keyboard users can reach controls. -
Enter or Space runs the control's action. It does not select or close the tab.
-
Each control has the role
buttonand uses itstooltipas its accessible name. -
Disabled controls are skipped by keyboard navigation.
-
The favicon is decorative and is hidden from screen readers. The tab's accessible name is still its title.
-
The close button on an inactive tab appears when the tab receives keyboard focus, so keyboard users can reach it.
-
In Windows high-contrast mode, system colors take precedence over custom tab colors.
Platforms that don't use Browser
If you build your own platform windows or use HERE Core Web layouts instead of HERE Core UI Browser, you can add controls to view tabs with the experimental renderCustomTabControls option of fin.Platform.Layout.create(). It works like renderCustomHeaderControls: your callback receives each tab element, and you add and manage your own DOM nodes. For details, see Add custom controls to view tabs. The icon view option described in Override or hide the view tab icon is also available on those platforms. In HERE Core Web layouts, you can set icon in the layout or snapshot configuration only, not with updateOptions().