Legacy skin API

The room.skin API of the legacy skin (basic) used by older chat rooms — jQuery DOM, menu bar, popups, nickname menu — and a mapping table for moving to the default skin.

Last updated October 17, 2026

On this page

The legacy skin (basic, shown in the admin page as "Legacy skin (old version)") is the skin older chat rooms use. New chat rooms are created with the default skin (basic-v2), and new features are added only to the default skin. This page lists room.skin for chat rooms that still use the legacy skin. For the default skin's API, see Skin UI API.

Page script U.chat(), events, room.action, channels (room.plugin), and the server API are the same in both skins. Only room.skin differs.

Check which skin is in use

The members of room.skin appear only after the skin builds the screen, so check inside after.create.

U.chat('my-room').on('after.create', function (room) {
	if (room.skin.menubar) {
		// Legacy skin
	} else if (room.skin.menu) {
		// Default skin
	}
});

Both skins have userMenu, so don't use it to tell them apart. You can change the skin in the admin page Skin menu or with the <u-chat skin="basic-v2"> attribute. Features that don't depend on the skin, such as member integration, keep working after the change.

Principles

  • The legacy skin works directly on the DOM inside the chat room iframe with jQuery (1.12). The skin's HTML structure and class names are not a fixed contract, and screens you modify may break when the skin changes.
  • Do all registration inside after.create. Before that, only room.skin.arguments and room.skin.messages exist.
  • Do not cover or remove the uchat logo or info display.

room.skin members

Name Description
room.skin.arguments Skin settings (admin page skin settings + skin_arguments attribute + global Uchat_argument). The skin fills in color defaults
room.skin.messages Skin UI text (current language)
room.skin.window The chat room iframe's window. jQuery is room.skin.window.$
room.skin.document The chat room iframe's document
room.skin.dom jQuery objects for the main areas. See dom below
room.skin.menubar The menu bar under the input box. See menubar below
room.skin.popup Popups (alert, confirm, input, select). See popup below
room.skin.userMenu The menu that opens when you click a nickname. See userMenu below
room.skin.scrollDown(force) Scrolls the chat to the bottom. Unless force is true, it does not scroll while the user has scrolled up

dom

All of these are jQuery objects for elements inside the chat room iframe.

Key Area
wrap The whole chat room (.wrap)
top Top title area (.top)
middle Chat and user list area (.middle)
content Chat scroll area (.contentWrap)
content_target Element that chat lines are appended to (.content)
userList User list scroll area (.userListWrap)
contour Divider between the user list and the chat (.contour)
bottom Bottom input area (.bottom)
inputContent Input box (.inputContent)
menubar Menu bar (.menuBar)
latest_chat Latest-message notice shown when scrolled up (.latest_chat)
setting, settingBG Settings window and its background (.settingContent, .settingBackground)
U.chat('my-room').on('after.create', function (room) {
	var $ = room.skin.window.$;
	var banner = $('<div/>').text('Event tonight at 8!').css({ padding: '6px', background: '#fff4cc' });
	room.skin.dom.middle.before(banner);
	$(room.skin.window).resize(); // If the height changed, let the skin re-layout
});

If you add elements and the height changes, call $(room.skin.window).resize() so the skin recalculates the chat area size. Insert user input with .text(), never .html().

Adds buttons to the menu bar under the input box.

U.chat('my-room').on('after.create', function (room) {
	room.skin.menubar.add({
		id: 'hello',
		title: 'Send a greeting',
		html: '<div>Hi</div>',
		condition: function (room, item) {
			return room.my.auth >= 1; // Hidden when false
		},
		onClick: function (room, item) {
			room.action.send('Hello!');
		},
	});
});
Key Description
id Required. Not registered if it's a duplicate. The button gets a menubar-<id> class
title Tooltip on hover
html Button content (HTML string). An empty <div> if omitted
icon Background image URL
condition(room, item) Not drawn if it returns exactly false
onClick(room, item) On click. item.event is the click event
beforeLoad(room, item), afterLoad(room, item) Before and after drawing. Not drawn if beforeLoad returns false
beforeUnload(), afterUnload() Before and after removal

item is the object you passed to add itself, and the jQuery object of the drawn button is stored in item.layout.

Function Description
add(item) Registers. Returns true on success, false if the ID is missing or a duplicate
refresh() Draws registered buttons according to condition
remove(id) Removes the registration
hide(), show(), toggle() Hide, show, or toggle the whole menu bar
list Array of registered items
  • add alone does not show anything on screen. The skin calls refresh() every time you join, so buttons registered in after.create appear on join. If you register after joining, call room.skin.menubar.refresh() yourself.
  • condition is recalculated only on refresh(). If condition becomes false for a button already drawn, its registration is removed, and it does not come back even if the condition becomes true later. To hide and show a button by permission, toggle() the item.layout you get in afterLoad instead of using condition.
  • remove(id) cannot remove the drawn button from the screen if the ID is not a number. When removing a button, also call item.layout.remove().
  • Default button IDs include scroll, bold, picture, and call_admin.

Shows popups inside the chat room. Only one popup is visible at a time; the rest wait in order. Every function returns a popup number, and you can close it with close(number). Results come through callbacks, not Promises.

var popup = room.skin.popup;

popup.confirm('Rock paper scissors', 'Accept the challenge?', function (ok) {
	// true for OK, false for Cancel, null if closed by clicking outside
});

popup.prompt('Nickname', 'Enter a new nickname.', 'default', function (value) {
	// The text entered, null if canceled
});

popup.select('Choose', 'Pick one.', { r: 'Rock', p: 'Paper', s: 'Scissors' }, function (key) {
	// The chosen key (string), null if canceled
});

var id = popup.alert('Notice', 'Closes in 10 seconds.');
setTimeout(function () { popup.close(id); }, 10000);
Function Value passed to the callback
alert(title, html, onOpen, onClose) onOpen(layer, pb, pu) is called when it opens, and onClose() when it closes. html is inserted as HTML, so escape user values
confirm(title, subtitle, fn) true, false, or null if you click outside
prompt(title, subtitle, value, fn) The text entered, null if canceled
password(title, subtitle, value, fn) Same as prompt, with the input masked
select(title, subtitle, list, fn) A key of list (object or array), null if canceled
add(html, type, fn(layer, pb, pu), after) A custom popup. layer is { close(), centering() }, pb is the background, pu is the popup body (jQuery). after() is called on close
close([number]) Closes that popup or removes it from the queue. Without a number, closes the popup currently shown
closeByType(type) Closes all popups with the same type given to add
  • Pressing Enter in an input popup is the same as OK.
  • When the browser window loses focus, the popup currently shown closes as if you clicked outside (confirm, prompt, and select return null). In flows where the user opens another window and comes back, write your code expecting the result may be null.

userMenu

Adds items to the menu that opens when you click a nickname. The menu is rebuilt on every click, so condition is recalculated each time.

room.skin.userMenu.add({
	id: 'profile',
	text: 'View profile',
	condition: function (room, data) {
		return data.target !== room.my.nick; // Hidden on my own nickname
	},
	onClick: function (room, data) {
		var user = room.user.get(data.target);
		if (user && user.id) window.open('/profile/' + encodeURIComponent(user.id));
	},
});
Key Description
id Required. Not registered if it's a duplicate (false)
text Item text
condition(room, data) Hidden if it returns exactly false. data.target is the clicked nickname string
onClick(room, data) On click. data is { event, id, target, messageUid }
Function Description
add(item) Adds one item
group(left, right) Puts two items side by side on one row
hr([{ condition }]) Divider. Its ID is always hr, so remove('hr') removes all dividers
remove(id) Removes all items with that ID (including default items)
list Array of registered items

Default item IDs include info, whisper, call, ignore, un_ignore, report, invite, individual, mute, unmute, ban, ip, op, deop, freeze, unfreeze, clearlog, removeChat, and logout. To hide a default item, call remove(id). Even if you hide a menu item, the server still checks permissions.

Legacy-only events

before.user.list and after.user.list (data is { list }) fire when the legacy skin opens the user list and receives the full list. The default skin receives the user list in parts, so these events do not fire there. All other events are the same as in Events.

Moving to the default skin

Task Legacy skin Default skin
Add a button menubar.add({ id, title, html, condition(room, item), onClick(room, item) }) menu.add({ id, label, title, condition(ctx), onSelect(ctx) })
Recalculate buttons menubar.refresh() menu.resolver.refresh()
Nickname menu userMenu.add({ id, text, onClick(room, data) }), data.target is the nickname userMenu.add({ id, label, onSelect(ctx) }), ctx.target is the user object
Hide default items userMenu.remove('ignore') Filter out skin.ignore with userMenu.resolver.add(...)
Popups Callbacks, such as popup.confirm(t, s, fn) Promises, such as dialog.confirm(t, s)
Insert an area Direct DOM, such as dom.middle.before(...) mount.messageList({ position: 'before', mount(host) }) and others
Overlay on the chat dom.content.append(...) + CSS mount.overlay({ layer, scope, mount(host) })
Decorate nicknames and messages Insert <style> via window.$ nickname.resolver, message.resolver
Scroll to bottom scrollDown(force) scrollToBottom(force)

The default skin does not support skin DOM, jQuery, or inserting <style>. For how to work after moving, start with Extending the skin UI.

Still not sure how it works? Ask your question on the contact board.

Business name UCHAT · CEO 김경욱 · Business registration no. 141-05-32832
Address 경기도 파주시 와석순환로 61 · Mail-order business registration no. 2013-경기파주-4485호
Phone 010-8677-8633 · Email support@uchat.io

ⓒ 2013–2026 UCHAT. All rights reserved.