Skip to content
Textual
Menu

Style migration guides

Changes to style templates and APIs across Textual 5, 6, and 7.

A reference from an earlier chapter

This material preserves the original Textual documentation and may describe older versions, former services, or superseded policies. It is not a statement of current availability or new Blendbyte commitments.

On this page

Style Developers: Migrating to 6.0.0

Introduction

For a style to be fully compatible with Textual 6, some changes are required.

This knowledge base article describes these changes.

Templates

Changes have been made to the following template files:

If a style overrides any of these template files, then please patch each file.

Click each file to view the difference between Textual 5 and Textual 6.

Conversation Tracking

To reduce clutter, Textual now has a built-in implementation of the conversation tracking feature.

The following functions no longer need to exist in a style's scripts.js file:

  • updateNicknameAssociatedWithNewMessage()

  • toggleSelectionStatusForNicknameInsideElement()

  • userNicknameSingleClickEvent()

Replacements

Parts of the file that callout to updateNicknameAssociatedWithNewMessage() can be replaced by ConversationTracking.updateNicknameWithNewMessage(), which takes the same arguments

Parts of the file that callout to userNicknameSingleClickEvent() can be replaced by ConversationTracking.nicknameSingleClickEventCallback(), which takes the same arguments

Example
Textual.newMessagePostedToView = function(line)
{
    var element = document.getElementById("line-" + line);

    ConversationTracking.updateNicknameWithNewMessage(element);
}

Textual.nicknameSingleClicked = function(e)
{
    ConversationTracking.nicknameSingleClickEventCallback(e);
}
Compatibility

Some styles, such as those that modify the layout of the DOM, will not be compatible with Textual's implementation of the conversation tracking feature. Those styles must implement their own logic for this feature.

Loading History

When a user clicks into a view for the first time, Textual loads their history from the previous session. It is recommended that a style fades in the history when it finishes loading, instead of having it appear suddenly.

To achieve this effect, set the opacity of the #historic_messages element to zero (0), which will hide this element by default. Set a transition on opacity changes, then change the opacity of this element when the history finishes loading.

To make this as easy as possible, the class .loaded is automatically added to the #historic_messages element when the history finishes loading.

Example
#historic_messages {
    transition: opacity 0.8s ease-in;
    opacity: 0;
    height: 0;
    margin: 0;
    padding: 0;
}

#historic_messages.loaded {
    opacity: 0.6;
    height: auto;
}
Alternatives

The callback Textual.viewFinishedLoadingHistory() is invoked when the history finishes loading. A style can use this callback to perform other transitions.

Custom Scrollbars for Dark Styles

Textual now adds the attribute customscroller=true to the <body> element when the user has macOS setup to always show scrollbars.

It is recommended that a styles with dark background setup a custom scrollbar appearance when this attribute is set so that the user does not see a white scrollbar on a dark background.

To make this task easier for style authors, the following code can be pasted into a style'sdesign.css file. This code creates a scrollbar which appears similar to the normal OS X scrollbar, but darker.

body[customscroller="true"]::-webkit-scrollbar {
	width: 17px;
}

body[customscroller="true"]::-webkit-scrollbar:horizontal {
	height: 0;
}

body[customscroller="true"]::-webkit-scrollbar-track {
	background: #393939;
	box-shadow: inset 1px 0px 0px 0px #4b4b4b;
}

body[customscroller="true"]::-webkit-scrollbar-thumb {
	background-color: #7c7c7c;
	border: 4px solid transparent;
	border-left: 5px solid transparent;
	border-radius: 20px;
	background-clip: content-box;
}

body[customscroller="true"]::-webkit-scrollbar-thumb:hover {
	background-color: #b0b0b0;
}

Overlay Color

Textual 6 allows users to view multiple channels at once. When a user is viewing multiple channels, a transparent view is laid on top of each channel except the frontmost one.

A style can change the color of this view in its styleSettings.plist file by defining a string value for the key named Channel View Overlay Color.

Hexadecimal notation, in RRGGBBAA order, is the only format supported by this setting.

Example
<key>Channel View Overlay Color</key>
<string>#00000066</string>

In this example, #00000066 produces the color black with 40% alpha.

app Object

The app object now uses callback functions instead of return values.

Example
app.inlineImagesEnabledForView(
    function(returnValue) {
        console.log(returnValue);
    }
);

Style Developers: Migrating to 7.0.3

Introduction

For a style to be fully compatible with version 7.0.3, some changes are required. This knowledge base article describes these changes.

baseLayout.mustache Template

Changes have been made to the baseLayout.mustache template file. If a style overrides this template file, then the changes made to this file must also be applied to that overridden by the style.

View Differences

baseLayout.css

CSS properties that should be consistent between all styles has been moved to a separate file named baseLayout.css which is now imported by baseLayout.mustache. Do not override baseLayout.css.

Addition of message_buffer container

The #message_buffer container is used by the new dynamic buffer feature to house messages that it maintains. Without this container, no messages will appear for the user because there is nowhere to place them.

Removal of historic_message container

The #historic_messages container has been removed. Messages from the previous session are now placed in the #message_buffer container with no distinguishable characteristics.

Message Buffer Session Indicator

The dynamic buffer inserts a marker stylized with the class .message_buffer_session_indicator to allow the user to distinguish which session a section of the scrollback is from.

A style can modify the properties of this class or replace the contents of the messageBufferSessionIndicator.mustache template to change the appearance of this marker.

Example Appearance

Style migration guides: archived screenshot 1

Example Appearance Properties

/* Message buffer session indicator */

.message_buffer_session_indicator {
    display: flex;
    display: -webkit-flex;
    padding: 0.5em 0;
}

.message_buffer_session_indicator > hr {
    background: #dbdbdb;
    border: 0;
    height: 1px;
    margin-top: 0.6em;
    flex: 1;
    -webkit-flex: 1;
}

.message_buffer_session_indicator > span {
    font-style: oblique;
    margin: 0 1em;
    color: #a6a6a6;
}

.message_buffer_session_indicator + #mark {
    display: none;
}

Dynamic Buffer

The new dynamic buffer feature removes messages from the top or bottom of the scrollback, depending on scroller position, when the number of visible messages exceeds an undocumented limit. A style should be designed with the understanding that there is no guarantee a particular message will be visible in the scrollback because of this.

If a style wants to keep a copy of a particular message so that it is not removed, then that message should be moved outside of the #message_buffer container.

Dynamic Buffer — core.js Changes

Message Added

The Textual.newMessagePostedToView(lineNumber) callback is now deprecated. It will be called if present, but it is preferred that you use Textual.messageAddedToView(lineNumber, fromBuffer) instead.

The second argument of the Textual.messageAddedToView callback is true when the message was restored by the dynamic buffer when the user scrolls towards it.

Message Removed

Added Textual.messageRemovedFromView(lineNumber) callback which is called when a message is removed by the dynamic buffer.

Other Changes

Some functions declared by the Textual object have moved. These functions were out of the scope of core.js to begin with which means this change should not break a custom style unless it was doing naughty things.

Scrolling Changes

The automatic scroller no longer uses a timer to detect when the height of the document changes. Instead, it is told when HTML will be added to or removed from the document.

If a custom style adds or removes HTML through the use of JavaScript, then it will need to tell the automatic scroller before it does so, so that the document can be automatically scrolled.

To accomplish this, call the prototype function prepareForMutation() on any element prior to modifying it.

You only need to call prepareForMutation() once within a synchronous block of code (such as a function). The automatic scroller will not scroll until the block of code completes.

Example

document.body.prepareForMutation();

document.body.firstChild.remove();

Style Developers: Migrating to 7.0.7

Introduction

For a style to be fully compatible with version 7.0.7, some changes are required. This knowledge base article describes these changes.

Templates

New Template Engine Version

To encourage style authors to adapt the newest features of Textual 7, the template engine version has changed.

To advertise support for the new template engine version, edit the style's styleSettings.plist file.

Modify the Template Engine Versions setting to include version 4.

Example
<key>Template Engine Versions</key>
<dict>
    <key>default</key>
    <integer>4</integer>
</dict>

New Attributes

Added data-appearance attribute to <body> which is either “dark” or “light”. The value of this attribute is automatically calculated based on the background color of the style.

Renamed Attributes

Nearly all custom attributes have been renamed to provide greater consistency and easier access through JavaScript. Below is a table of all attributes that have been renamed.

Old Name New Name
bgcolor-number data-background-color
channelname data-view-name
color-number data-foreground-color
coloroverride data-override-color
command data-command
customscroller data-custom-scroller
highlight data-highlight
ltype data-line-type
mtype data-member-type
selected data-selected
systemversion data-system-version
timestamp data-timestamp
viewtype data-view-type

Attribute Placement

All custom attributes that appeared in <html> have moved to <body>.

Nickname Colors

The logic that determines which color is assigned to a nickname has changed. Nicknames are now assigned a consistent, unique color instead of picking from a pool of thirty possibilities.

Color Values

The new nickname colors are literal RGB or HSL values. For example: hsl(293, 81%, 69%)

Templates

When rendering each template, the color of each nickname is set using a the style attribute.

A custom attribute (formally colornumber) is no longer used to set the color in a rendered template.

It is possible for a custom color to be assigned to a nickname using the setcolor command. To provide a style more context, the attribute data-override-color is added to a nickname when a custom color is set.

Variations In Color

The shade of colors assigned will vary depending on whether a style has a light background or dark background. A light background will produce colors that are slightly darker whereas a dark background will produce the opposite.

A style's Underlying Window Color setting is used to infer which shade of colors to assign.

Optionally, a setting named Nickname Color Style can be set. This setting instructs Textual to use a specific shade of colors, regardless of the style's background.

Possible values:

  • HSL-light — colors that are better suited for a light background.

  • HSL-dark — colors that are better suited for a dark background.

Example
<key>Nickname Color Style</key>
<string>HSL-dark</string>

Inline Media

Textual is now capable of showing more than images inline with chat.

Styling

Each type of media that is shown inline with chat can be customized by adding rules to its CSS selector. Selectors follow a common format: they begin with “inline” and a descriptor follows.

Examples
.inlineImage
.inlineVideo
.inlineGfycat
.inlineTwitchClips
.inlineTwitchLive
.inlineVimeo
.inlineYouTube

This list contains example selectors. This list is incomplete.

Style Developers: Migrating to 5.1.2

Off-the-Record Messaging (OTR)

The line type off-the-record-encryption-status has been added.

Style this line type so that it immediately grabs the user's attention when it appears.

For example:

/* off-the-record-encryption-status Message Event */
body div.line[ltype="off-the-record-encryption-status"] .message {
    color: #ff0000;
    font-weight: 700;
}

Other Changes

The line type dccfiletransfer has been renamed to dcc-file-transfer.

Style Developers: Migrating to 5.1.4

Improved Scroll Performance

Historic messages are now placed inside the container named “historic_messages,” which is a child of “body_home”

Style the “historic_messages” container, rather than individual messages, to improve scrolling performance.

Changes to CSS

To support the new container, the following changes can be made to a style's design.css file:

-           .historic {
+           #historic_messages,
+           #body_home > .historic {

Changes to Templates

The following changes were made to the baseLayout.mustache template file:

-           <div id="body_home"></div>
+           <div id="body_home">
+                 <div id="historic_messages"></div>
+           </div>

Style Developers: Migrating to 5.2.0

Changes

  • Added Textual.dateChanged(dayYear, dayMonth, dayDay) callback which is called at midnight.

  • The Textual.preferencesChanged() callback is now opt-in. To opt-in: add the key “Post Textual.preferencesDidChange() Notifications” as a boolean to a style's styleSettings.plist file.

  • Removed the Textual.sidebarInversionPreferenceChanged() callback in favor of Textual.preferencesChanged() and app.sidebarInversionIsEnabled()

Source material

Original Codeux material preserved from the knowledgebase snapshot of 9 September 2026. Formatting and internal links have been adapted for this site.

Need a hand? Contact us about this guide.

Textual screenshot