UI Components

In addition to the standard UI Components that the XDK ships with, each platform provides some additional UI Components that are commonly needed for that platform.

The Notifier


The Notifier is not part of the default npm build and must be imported

import "@layerhq/web-xdk";
import "@layerhq/web-xdk/ui/components/layer-notifier";

The Notifier is a simple UI Component for managing notifications of new messages. It provides the following properties:

Name Type Default Description
notifyInBackground String desktop When a new message is received while the browser tab does not have focus, what kind of notification should it use? desktop, toast, or none?
notifyInForeground String none When a new message is received while the browser tab has focus, what kind of notification should it use? desktop, toast, or none?
iconUrl String “” Provide an icon for your app if you want notifications to contain your app’s icon. Leave it unset to use the message sender’s IdentityavatarUrl instead.
timeoutSeconds Number 30 Number of seconds before the desktop or toast notification is automatically closed. Clicking the notification will also close it.
layer-message-notification Event This event is triggered before showing a notification, call evt.preventDefault() if the notification is not needed.
layer-notification-click Event This event is triggered when the user clicks on a Notification.
notifyInTitlebar Boolean true Enable/disable showing an unread indicator in the titlebar. Prefixes the title with ⬤
notifyCharacterForTitlebar String Set the titlebar new-message-indicator character

The Notifier currently shows only a single toast notification; <layer-notifier /> shows itself when presenting the notification rather than generating new notification dialogs with each event. This means that your Notifier should go in your document’s body, not its head.


Given the following setup, we can start to look at how the Notifier integrates into an application:

  <layer-notifier notify-in-foreground="toast" notify-in-titlebar="false"></layer-notifier>
  var conversationsList = document.querySelector('layer-conversation-list');
  var conversationView = document.querySelector('layer-conversation-view');

Preventing Notifications

The Notifier does not know what ConversationView the user sees nor what Conversation is shown by that View. Therefore the Notifier cannot reason about whether a notification should or should not be shown. Your application on the other should know this and can call evt.preventDefault() to prevent a notification from showing if the new Message is in a Conversation that is already showing.

document.body.addEventListener('layer-message-notification', function(evt) {
  var message = evt.detail.item;
  var appIsInBackground = evt.detail.isBackground;
  var conversationId = message.conversationId;

  // If the Conversation View is showing the Conversation, and the window is in the foreground,
  // don't show the notification
  if (conversationView.conversation.id === conversationId && !isBackground) {

Handling User Click on Notifications

When the user clicks on a Notification, the typical expected behavior is for the ConversationView to set its conversation property to show the Message associated with that Notification. This shows the Conversation to the user. However, an app could have multiple ConversationView on the page, so the application itself must explicitly respond to the user click and tell the appropriate ConversationView to load the Conversation.

document.body.addEventListener('layer-notification-click', function(evt) {
  var message = evt.detail.item;
  var conversationId = message.conversationId;

  // Update the selected Conversation in the Conversation List
  conversationsList.selectedId = conversationId;

  // Update the Conversation viewed in the Conversation View
  conversationView.conversationId = conversationId;

Simplifying with listenTo

The following example lets us remove all of the above sample code and simply configure the ConversationView and ConversationListPanel.List to monitor for events from the Notifier and prevent notifications when needed, and update their state when the user clicks on notifications:

<layer-notifier id="mynotifier" notify-in-foreground="toast" notify-in-titlebar="false"></layer-notifier>
<layer-conversation-list listen-to="mynotifier"></layer-conversation-list>
<layer-conversation-view listen-to="mynotifier"></layer-conversation-view>

The Send Button


The SendButton is not part of the default npm build and must be imported

import "@layerhq/web-xdk";
import "@layerhq/web-xdk/ui/components/layer-send-button";

The ComposeBar consists of just a text input. Messages are sent by hitting the ENTER key on your keyboard.

This is not suited to all apps, so a Send Button can be easily added using theConversationViewreplaceableContent to configure composeButtons area:

myConversationView.replaceableContent = {
  composerButtonPanelRight: '<layer-send-button></layer-send-button>'

The SendButton will emit a SendButtonlayer-send-click event when its clicked; this will be intercepted by the ComposeBar which will send the message. No configuration is needed once the SendButton is added to the ComposeBar.

Name Type Default Description
text String SEND Get/Set the text of the button
SendButtonlayer-send-click Event Event is triggered when the user clicks the button

The File Upload Button


The FileUploadButton is not part of the default npm build and must be imported

import "@layerhq/web-xdk";
import "@layerhq/web-xdk/ui/components/layer-file-button";

The ConversationView supports drag and drop of files out-of-the-box. Set the ConversationViewisDropTargetEnabled to false to disable Drag and Drop support. Adding a FileUploadButton is simply adding a more visible way of attaching files; your UI already supports attaching files. Alternatively, for mobile devices where drag-and-drop is not an option, the FileUploadButton may be required for your project.

The ComposeBar consists of just a text input. If you want users to be able to open a File Browse Dialog and upload a file, add this button using the ConversationViewreplaceableContent to configure composeButtons area:

myConversationView.replaceableContent = {
  composerButtonPanelRight: '<layer-file-upload-button></layer-file-upload-button>'

The FileUploadButton will emit a FileUploadButtonlayer-file-selected event when a file is selected; this will be intercepted by the ComposeBar and will take the file(s) selected, generate MessagePart objects for them, and send them as a Message. No configuration is necessary.

If you want to customize this behavior; you can intercept the Message its generating using the FileUploadButtonlayer-models-generated event:

var uploadButton = document.createElement('layer-file-upload-button');
myConversationView.replaceableContent = {
  composerButtonPanelRight: uploadButton

uploadButton.addEventListener('layer-models-generated', function(evt) {
  var messageModels = evt.detail.models;
  var rootModel;

  // If only one Message Type Model (i.e. a Model for a Text Message), that will be our entire Message
  if (messageModels.length === 1) {
    rootModel = messageModels[0]

  // If multiple Message Type Models, generate a Carousel and send them within the Carousel:
  else {
    const CarouselModel = Layer.Core.Client.getMessageTypeModelClass('CarouselModel');
    rootModel = new CarouselModel({
      items: messageModels,

  // Prevent the message from being sent via the default handler so that we can send it ourselves

  // Generate a Message from the model, and optionally customize the Message before sending
  rootModel.generateMessage(this.conversation, (message) => {
    message.addPart(new Layer.Core.MessagePart({
      mimeType: 'application/signature',
      body: 'I approve this attachment'

The above handler:

  1. Defines how a collection of MessageTypeModel objects will be sent
  2. Prevents the default ComposeBar from sending the Message
  3. Modifies the Messageparts data prior to sending the Message.
Name Type Default Description
accept String */* Regular expression of acceptable file types
multiple Boolean false File Upload allows for more than one file at a time?
FileUploadButtonlayer-models-generated Event Event is triggered when MessageTypeModel objects have been generated based on the files selected and are about to be turned into a Message and sent.

By default, the File Upload Button will create:

  • An Image Message if the file’s type matches Layer.Settings.imageMIMETypes and the Image Message is part of the build
  • A Video Message if the file’s type matches Layer.Settings.videoMIMETypes and the Video Message is part of the build
  • An Audio Message if the file’s type matches Layer.Settings.audioMIMETypes and the Audio Message is part of the build
  • A File Message if the File Message is part of the build

The Date Boundary

All Lists are populated with List Items such as ConversationListPanel.Item.Conversation and MessageListPanel.SentItem. All List Items provide MessageListPanel.SentItemcustomNodeAbove and MessageListPanel.SentItemcustomNodeBelow which are properties that let you assign DOM nodes to go above or below the List Item. A simple example of this is a Date Boundary that goes between two Message objects that are sent on different dates:

if (listItem1.item.sentAt.toDateString() !== listItem2.item.sentAt.toDateString()) {
  listItem2.customNodeAbove = "<div class='date-separator'>" +
    listItem2.item.sentAt.d.toLocaleDateString() + "</div>";

All Lists (and the Conversation View) provide a ConversationViewonRenderListItem property that allows your callback to be called whenever a List Item is rendered or impacted by a new List Item being rendered near to it:

conversationView.onRenderListItem = function(widget, messages, index, isTopItem) {
  try {
    const message = widget.item;
    const needsBoundary = index === 0 ||
      message.sentAt.toDateString() !== messages[index - 1].sentAt.toDateString();
    if (needsBoundary) widget.customNodeBefore = "<div class='date-separator'>" +
      listItem2.item.sentAt.d.toLocaleDateString() + "</div>";
  } catch(e) {}

However, the UIUtilsdateSeparator() utility is provided to simplify this:

conversationView.onRenderListItem = Layer.UI.UIUtils.dateSeparator;

The Menu Button


The MenuButton is not part of the default npm build and must be imported

import "@layerhq/web-xdk";
import "@layerhq/web-xdk/ui/components/layer-menu-button";

MenuButton provides a simple button for popping up a menu. These are useful for putting next to each Conversation in a Conversation List, or perhaps next to each Message in a Message List.

Name Type Default Description
getMenuItems Function Function for generating the Menu Items to show; this function is called at click time.
item Root Message, Conversation or other List Item data that tells the Menu Button what context its being executed in (for use when there is a Menu Button next to each item in a List).

MenuButtongetMenuItems returns an array of Menu Items which are represented as:

  text: "My Menu Item",
  method: function(item) {
    alert("My Menu Item Clicked; operating on " + item.toString());

In the above example, the Menu Item’s text and handler are specified; and the item input to the handler only exists if MenuButtonitem has a value.

A simple example:

var menuButton = document.createElement('layer-menu-button');
menuButton.item = message;
menuButton.getMenuItems = function(item) {
    return [
        text: "delete",
        method: function() {item.delete(Layer.Constants.DELETION_MODE.ALL);}

Typically, the Menu Button is built in as a default for various UI Components that can be replaced using the replaceableContent property. For UI Components where it is built in, those UI Components will also have a getMenuItems method:

These properties can be set the same as you would set an individual Button’s MenuButtongetMenuItems. The following controls all MenuButtons in all of the Message List Items within the Conversation View, and provides the ability to delete each message:

conversationView.getMenuItems = function(item) {
    return [
        text: "delete",
        method: function() {item.delete(Layer.Constants.DELETION_MODE.ALL);}
Core Components Templates