Techsenger TabShell is a platform for building tab-based applications in JavaFX using the MVP pattern.
The platform consists of two parts: the core and ready-made components. The core includes the core shell and classes for creating components. Ready-made components are used as needed and significantly reduce the development time of the final application.
TabShell is built on top of the PatternFX framework.
- Demo
- Features
- When to Use?
- Modules
- Core Components
- Layout Components
- Shared Components
- Dialog Components
- DevTools Components
- Web Components
- Quick Start
- Requirements
- Dependencies
- Code Building
- Running Demo
- License
- Contributing
- Support Us
Key features of TabShell include:
- Dynamically configurable menu.
- Support for different types of workspace.
- Abstract classes to simplify component development.
- A set of ready-made components that can be used out of the box.
- Support for different layouts, including a docking layout.
- Set of devtools for inspecting the application at both the component layer and the JavaFX scene graph layer.
- Ability to preserve component history.
- Support for inline dialogs with two scopes — shell and tab.
- Window styling that matches the theme.
- Support for 7 themes (4 dark and 3 light).
- API for working with all colors in the palettes of all themes
- Styling with CSS.
Currently, the primary ready-made components include:
- Shell.
- Different layouts.
- Terminal.
- Text viewer/editor.
- Hex editor.
- Simple web browser.
- Dialogs.
- Devtools.
With the widespread adoption of web browsers, tab-based interfaces have become familiar and intuitive for most users. TabShell leverages this familiarity to create desktop applications where users can comfortably work with multiple contexts simultaneously.
Perfect for:
- Code editors where developers work with multiple files.
- Enterprise systems managing different data entities like orders, customers, and products.
- Database administration tools running parallel queries and comparisons.
- Data analysis applications with multiple datasets and visualization tabs.
- File managers and content browsers handling various file types.
- Monitoring dashboards displaying different metrics and logs
- Customer support systems with multiple client sessions open
The tab-based approach allows users to maintain workflow context while switching between different tasks, making complex applications more intuitive and productive.
The platform consists of the following modules:
- Material — provides UI elements (menus, text areas, etc.) and supporting classes.
- Core — includes the shell itself, base classes for component development, settings, and core utility classes.
- Layout — offers abstract components for creating tabs with various layouts.
- Shared — includes components that are used by other components from different modules.
- Storage — provides abstractions for working with file systems. The module includes a default implementation for the local file system. Additional storage providers (for Google Drive, Dropbox, FTP, and similar) can be implemented separately.
- Dialogs — provides ready-to-use dialogs: alert, file chooser, confirmation etc.
- Text — contains text viewer and editor components.
- Hex — contains hex editor.
- Terminal — includes a terminal emulator component.
- Registrars — provides default registrars (for menu items, etc.).
- Icons — contains the Material Design Icons font and module-specific stylesheets that utilize these icons. To use custom icons instead, simply create your own stylesheets and add them to Shell.
- DevTools — contains tools for exploring component tree and JavaFX scene graph.
- Web — includes a simple web browser built on JavaFX WebView.
- Demo — showcases TabShell's core functionality, provides examples for building custom components, and presents all ready-made components.
If you don't plan to use ready-made components, just three modules (material, core, layout) are sufficient to run TabShell and develop custom components. See Running Demo for details.
These components form the architectural foundation of the platform, and all higher-level platform components are built upon them.
TabShell is built on top of the PatternFX platform, which supports working both with and without a component tree.
In TabShell, all components form a tree structure, and multiple trees may exist depending on the number of Windows.
For this reason, all TabShell core components inherit from the Parent and Child components provided by PatternFX.
Each component is defined by an interface accompanied by a base implementation. This approach ensures loose coupling
while still providing default implementations out of the box. It also allows developers to replace or extend the default
behavior with custom implementations when required. For instance, the platform consistently references Shell
through the ShellFxView interface rather than a concrete class.
When working with components, there are several important points to keep in mind:
-
A component must be initialized manually (by calling the
initialize()method) and is typically deinitialized by its parent component (by calling thedeinitialize()method). This approach allows developers to perform additional configuration or setup after initialization but before attaching the component to the component tree. -
Working with components involves maintaining two hierarchies — the component tree and the JavaFX node tree. Therefore, any addition or removal of a component must be reflected in both structures. For example, removing a component from the node tree without removing it from the component tree will result in a memory leak. DevTools provide the ability to inspect and monitor both hierarchies.
Shell is the main and top-level component and is the only fully ready-to-use component in the core module. It is
responsible for the following tasks:
- Window management.
- Dynamic menu management.
- Shell-scoped dialog management.
- Theme management.
- Workspace management.
The Shell core does not contain any business logic. It is only a shell for tabs that contain logic.
Working with the main menu of the Shell is carried out in two directions:
- Configuring menu elements
- Managing the state of elements and responding to user actions
The configuration of menu elements is performed dynamically and in any order, with the final result being unknown in advance. This feature is crucial in cases where plugins/extensions are used, as they can be added/removed dynamically by the user. Each plugin may introduce its own menu items and interact with existing menus. Therefore, it is impossible to predict the final structure of the menu that the user will work with.
The implementation of this feature is structured as follows. There are three key elements: the menu, the group, and the
item. Each element has its own name, which is used for identification. A menu consists of groups separated by a
separator. Items are added to groups, and empty groups are ignored. The factories of all three elements are
registered/unregistered in the ControlRegistry. When the menu needs to be updated, this ControlRegistry is used
by Shell to construct the final menu.
The MenuManager is responsible for managing the state of menu elements and responding to their actions. It
interacts with a component that provides a port implementing the MenuAwarePort interface. The algorithm works as
follows: Shell tracks the component that currently has focus and stores it in ShellFxView#focusedProperty().
At the same time, the focused component may not participate in menu formation (for example, it could be just a toolbar).
Therefore, after the focused component changes, Shell searches from the focused component up to the root of the
tree — the Shell — for the first component whose port implements MenuAwarePort. Note that Shell can also form
the main menu, but this is usually done only when the workspace is empty. See also ShellFxView#menuAwareProperty().
It is also important to remember that the MenuManager also interacts with MenuAwarePort when the user uses accelerators.
To gain a complete understanding of working with the menu, it is recommended to familiarize yourself with the
MenuAwarePort interface, experiment with the menu in the demo, and pay attention to log messages at the debug level.
The second key part of TabShell is the workspace, which represents one of the available layouts. TabShell supports two primary workspace types:
- Browser-like. This workspace is created using the
TabHostcomponent with a flag indicating that it is a workspace. Additionally, the tabs added to thisTabHostcontain a docking layout created with theDockHostcomponent. - IDE-like. This workspace is a straightforward docking layout created with the
DockHostcomponent.
Tab is an abstract component used for creating custom tab implementations. In TabShell, Tab is one of the central
platform components, since the primary application functionality is delivered through tabs.
Tab can be added to any component that implements the TabContainer interface. The platform
provides two components that implement this interface: TabHost and TabDock, where TabDock extends TabHost.
Area is an abstract base component that represents a rectangular region. Naturally, AreaFxView#getNode() returns a
Region.
Page is a component that represents a titled, selectable element. A key feature of this component is its lazy
initialization. For example, if a container displays one of N Pages, only the Page that the user actually chooses to
view will be initialized.
Page can be added to any component that implements the PageContainer interface. The default implementation of
this interface is PageHost.
All Popups in TabShell are inline and have a scope that affects what will be blocked when the Popup is open.
Inline Popups are components that appear embedded within the current application window, typically overlaid on top
of the existing content. They are contextually tied to a specific section (e.g., a Shell or Tab) and do not
create a separate OS-level window. In contrast, modal window Popups (or native Popups) open as standalone
OS-managed windows with their own frames and system controls, completely independent of the parent UI.
There are two types of scope: Shell and Tab. Popups in the Tab scope are bound to a specific tab and are visible
only while that tab is open. Popups in the Shell scope are global to the shell and remain visible even when all
tabs are closed.
Popup can be added to any component that implements the PopupContainer interface. The platform provides two
components that implement this interface: Tab and Shell.
All Dialogs in TabShell are inline, asynchronous, and have a scope that affects what will be blocked when the
Dialog is open. Dialogs extend Popups and represent a specialized type of Popup with dialog-specific behavior.
Asynchronous Dialogs allow the program to continue running while the Dialog is open, relying on callbacks, promises,
or event listeners. These avoid UI freezes and enable background tasks but require handling user responses indirectly,
often via lambda functions or observable states. Synchronous Dialogs, conversely, block the application's execution
flow until the user responds, pausing all other interactions (e.g., showAndWait() in JavaFX). They simplify code logic
by enforcing a linear sequence but risk freezing the UI during operation. The key distinction lies in control flow:
asynchronous Dialogs prioritize responsiveness, deferring action until the user completes the interaction, while
synchronous ones enforce immediate resolution. Modern UI design increasingly favors async approaches for scalability
and user experience.
There are two types of scope: Shell and Tab. If a Dialog has a Shell scope, the user will not be able to do
anything in Shell while this Dialog is displayed until it is closed. If a Dialog has a Tab scope, only the
tab that triggered the Dialog will be blocked when it is displayed. All other tabs, the main menu, etc., will be
available to the user.
Dialog can be added to any component that implements the DialogContainer interface. The platform provides two
components that implement this interface: Tab and Shell.
Layout components are responsible for arranging Tab, Page, and, in some cases, Area components and their derivatives.
TabHost is the primary component that can contain Tab components; therefore, it implements the TabContainer interface.
This component provides all the necessary APIs for working with tabs — adding, selecting, removing, transferring
tab ports, and more.
DockHost is the main component of the docking layout and one of the most complex components in the platform.
Before describing how it works, let’s examine its child components.
SplitSpace is a component that extends Area. It internally contains a SplitPane node and is responsible for
arranging child components either vertically or horizontally.
TabDock extends TabHost, meaning it can contain tabs. In addition, it introduces docking-specific functionality
such as dragging an entire TabDock from one layout position to another, collapsing it into a SideBar, and
similar behaviors.
SideBar is a component that displays collapsed TabDock instances. It is important to note that a SideBar can
be shown even when it contains no collapsed TabDock components, using SideBarPolicy. This is useful when the
SideBar is intended to host additional UI elements besides collapsed TabDocks.
Now that the components are introduced, let’s outline how everything works together. A docking layout is always
represented as a tree. Therefore, the layout must be constructed using SplitSpace nodes. A SplitSpace can contain
other SplitSpace instances (to change orientation), TabDock instances (to host Tabs), or any Area-based
component as a leaf node. After constructing the component tree, the method Composer#setRoot(SplitSpaceFxView<?>)
must be called.
In addition to building the component tree, DockHost requires specifying the main component — the component relative
to which all other components are positioned. The main component can be an Area or any class derived from it
(including TabDock and SplitSpace). It is set using the method Composer#setMain(AreaFxView<?>).
PageHost is a simple component that displays Page components and performs their lazy initialization.
It can be used to display navigable pages with a menu-like structure (similar to a website layout). Additionally, it is commonly used in dialogs with a navigation tree on the left and a content page on the right. A classic example is a settings dialog.
Shared components are auxiliary components built on top of Core components and used by components from other modules.
FindBase is an abstract base search component that contains the entire search view implementation, including both
submit search and instant search functionality. Since child components may be of different types (toolbar, panel, etc.),
this component includes only minimal CSS styling. It is important to note that this component does not contain any
logic for executing the search itself. At the same time, the base *FindPort interfaces are provided without
implementations.
FindPanel is an abstract class for find panels that are placed at the bottom of other components, such as a terminal,
text editor, hex editor, etc. It is important to note that this component does not contain any logic for executing the
search itself.
In this section, the dialogs from the dialogs module are described. This module contains implementations of the most
commonly used dialogs.
AlertDialog is a dialog for common user notification scenarios such as informational messages, warnings, errors,
and confirmation requests.
FileChooserDialog is a dialog for selecting a file when opening or saving. The dialog type is defined using the
FileChooserType enumeration.
It is important to note that this dialog works with files provided by classes from the storage module. This makes
it possible to use the dialog with virtually any file storage implementation, provided that an appropriate FileStorage
implementation is supplied.
NameValueDialog is a simple dialog for displaying name–value pairs. The parameter name is shown in a TextField,
while the value is displayed in a TextArea.
DevTools components are tools for inspecting and analyzing the application at two levels: the component tree and the JavaFX scene graph. They are primarily intended for developers building components on top of the platform.
This component is a container for Tab components and provides shared tab management mechanisms. It can be added to
any layout, whether a simple layout or a docking layout.
This component allows exploring the tree of active components and inspecting their properties. In addition, it provides information about the class hierarchy of the selected component.
NodeTab is a tool for analyzing the JavaFX scene graph. It allows traversing the node tree and inspecting node
properties. The component also enables opening reference documentation (Javadoc) for both classes and their properties.
This component allows recording node events. It can operate with or without filters. Events can be filtered by selected component, message, event type, and other criteria.
This component allows inspecting which stylesheets are applied to nodes within the scene.
This component provides access to platform settings, system properties, and environment variables.
Web components are components that reside in the web module. Together, they form a simple web browser built on JavaFX WebView.
WebBrowserTab is the main component. It represents a Tab for the Shell and can contain all other web components.
WebToolBar is a component that represents the browser’s ToolBar.
To get started with TabShell, it is recommended to follow these steps:
- Familiarize yourself with the PatternFX framework, the MVP template, and its demo.
- Explore and run the demo. See Running Demo for details.
The library requires Java 25 and JavaFX 25.
This project is available on Maven Central. Minimal set of required dependencies:
<dependency>
<groupId>com.techsenger.tabshell</groupId>
<artifactId>tabshell-material</artifactId>
<version>${tabshell.version}</version>
</dependency>
<dependency>
<groupId>com.techsenger.tabshell</groupId>
<artifactId>tabshell-core</artifactId>
<version>${tabshell.version}</version>
</dependency>
<dependency>
<groupId>com.techsenger.tabshell</groupId>
<artifactId>tabshell-layout</artifactId>
<version>${tabshell.version}</version>
</dependency>
To build the library use standard Git and Maven commands:
git clone https://github.com/techsenger/tabshell
cd tabshell
mvn clean install
To run the demo, execute the following commands in the project root:
cd tabshell-demo
mvn javafx:run
Please note, that debugger settings are in pom.xml file.
Techsenger TabShell is licensed under the Apache License, Version 2.0.
We welcome all contributions. You can help by reporting bugs, suggesting improvements, or submitting pull requests with fixes and new features. If you have any questions, feel free to reach out — we’ll be happy to assist you.
You can support our open-source work through GitHub Sponsors. Your contribution helps us maintain projects, develop new features, and provide ongoing improvements. Multiple sponsorship tiers are available, each offering different levels of recognition and benefits.



