Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active October 23, 2025 03:53
Show Gist options
  • Select an option

  • Save 0xdevalias/3d2f5a861335cc1277b21a29d1285cfe to your computer and use it in GitHub Desktop.

Select an option

Save 0xdevalias/3d2f5a861335cc1277b21a29d1285cfe to your computer and use it in GitHub Desktop.
Custom themes/CSS styling hacks/overrides for Beeper (universal chat app aggregator, built on top of matrix)

Beeper Custom Theme Styles

devalias' Beeper CSS Hacks

See devalias-beeper-css-hacks.css for the customisations I am using myself.

You'll also find a number of hacks/techniques on my theme issue:

See Also

My Other Related Deepdive Gist's and Projects

Bonus Section

Accessing the Matrix client from within Beeper / Electron's DevTools

mxMatrixClientPeg.matrixClient

eg. to send a message:

const roomId = "!KlacjKWnARbprTLuRM:nova.chat";

mxMatrixClientPeg.matrixClient.sendMessage(roomId, {
  msgtype: "m.text",
  body: "This is a test message sent using mxMatrixClientPeg.matrixClient.sendMessage"
})

Accessing the React internals for the Beeper 'Rooms' component using Beeper / Electron's DevTools

const el = $('#matrixchat > .mx_MatrixChat_wrapper > .mx_MatrixChat > .bp_LeftPanel > .bp_LeftPanel_contentWrapper > .bp_LeftPanel_content > .rooms')
 
const elProps = Object.getOwnPropertyNames(el);
 
const elReactFiberKey = elProps.filter(k => k.includes('__reactFiber'))
const elReactPropsKey = elProps.filter(k => k.includes('__reactProps'))

const elReactInternals = {
  reactFiber: el[elReactFiberKey],
  reactProps: el[elReactPropsKey],
}
 
//console.log(elReactInternals)

const UnreadList = elReactInternals.reactProps.children[3].props.children[0]
const ReadList = elReactInternals.reactProps.children[3].props.children[1]

console.log('Inbox Chats', UnreadList.props.unreads)
// (303) [Room, Room, ...]

console.log('Archived Chats', ReadList.props.rooms)
// (1633) [Room, Room, ...]

Various Beeper Inbox Selectors (Favourite, Pinned, Not Pinned, Unread, Etc)

// Inbox - Favourites List
$$('.rooms > .favourites [data-type="bp_RoomTile"]')

// Inbox - Favourites List - Unread
$$('.rooms > .favourites .isUnread[data-type="bp_RoomTile"]')

// Inbox - Favourites List - Muted
$$('.rooms > .favourites .isMuted[data-type="bp_RoomTile"]')

// Inbox Chats
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]')

// Inbox Chats - Favourite
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:has([data-src="img/beeper/heart-filled16.b7ad82d.svg"])')

// Inbox Chats - Pinned
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])')

// Inbox Chats - Not Pinned
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"]))')

// Inbox - Unread - Favourite-Avatar
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')

// Inbox - Unread - Avatar
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')

// Inbox - Unread - Combined
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon, .rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')

Counting chats that match the above

This is a bit of a hacky WIP / PoC, but it seems to do the trick:

/*************************/
/* Counting Chat Types */
/************************/

/* Initialize counters */
.rooms {
  counter-reset: unread favourite pinned not-pinned;
}

/* Increment favorites */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/heart-filled16.b7ad82d.svg"] {
  counter-increment: favourite;
}

/* Increment pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/pin-filled16.b7cb2af.svg"] {
  counter-increment: pinned;
}

/* Increment not pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])) {
  counter-increment: not-pinned;
}

/* Increment unread */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon,
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon {
  counter-increment: unread;
}

/* Show Counters */
/*.rooms::after {
  content: "Counters: Unread (" counter(unread) "), Favourite (" counter(favourite) "), Pinned (" counter(pinned) "), Not-Pinned (" counter(not-pinned) ")"
}*/
.rooms::after {
  content: "Unread (" counter(unread) "), Not-Pinned (" counter(not-pinned) ")";

  font-size: 10px;

  position: absolute;
  top: calc(31vh - 2px);
  left: calc(8vw - 4px);

  background-color: dimgrey;
}

/* Set position to relative for the hovered element */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] {
  position: relative;
}

/* Show the not-pinned counter on hover */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:hover::before {
  content: "Not-Pinned above: " counter(not-pinned);

  font-size: 10px;
  white-space: nowrap;

  position: absolute;
  bottom: 5px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;

  border-radius: 3px;
  padding: 2px;

  background-color: dimgrey;
}

Beeper - Password Reset (JWT)

See the following repo for my notes (formatted as a ChatGPT prompt) on implementing a JWT-based password reset flow using Beeper's new 'email login' flow; as well as Proof of Concept (PoC) code implementing a CLI tool for using this flow.

/* Will this work with https://developer.mozilla.org/en-US/docs/Web/CSS/@import ? */
/**********************************************/
/* Hide Left Panel/Sidebar While Chat Is Open */
/**********************************************/
/*.mx_MatrixChat:has(> .bp_MainPanel > .mx_RoomView) > .bp_LeftPanel {
display: none;
}
.mx_MatrixChat:has(> .bp_MainPanel > .mx_RoomView) > div:has(.spaceBar) {
display: none;
}*/
/*************************/
/* Counting Chat Types */
/************************/
/* Initialize counters */
.rooms {
counter-reset: unread favourite pinned not-pinned;
}
/* Increment favorites */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/heart-filled16.b7ad82d.svg"] {
counter-increment: favourite;
}
/* Increment pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/pin-filled16.b7cb2af.svg"] {
counter-increment: pinned;
}
/* Increment not pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])) {
counter-increment: not-pinned;
}
/* Increment unread */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon,
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon {
counter-increment: unread;
}
/* Show Counters */
/*.rooms::after {
content: "Counters: Unread (" counter(unread) "), Favourite (" counter(favourite) "), Pinned (" counter(pinned) "), Not-Pinned (" counter(not-pinned) ")"
}*/
.rooms::after {
content: "Unread (" counter(unread) "), Not-Pinned (" counter(not-pinned) ")";
font-size: 10px;
position: absolute;
top: calc(31vh - 2px);
left: calc(8vw - 4px);
background-color: dimgrey;
}
/* Set position to relative for the hovered element */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] {
position: relative;
}
/* Show the not-pinned counter on hover */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:hover::before {
content: "Not-Pinned above: " counter(not-pinned);
font-size: 10px;
white-space: nowrap;
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
border-radius: 3px;
padding: 2px;
background-color: dimgrey;
}
/**********************/
/* Main Settings Menu */
/**********************/
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Chat Networks"] {
display: none;
}
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Mark All As Read"] {
display: none;
}
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Check for Update"] {
display: none;
}
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Download Mobile App"] {
display: none;
}
/*******************/
/* Settings Dialog */
/*******************/
/* Settings Dialog - Tab - Chat Networks */
.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_chatSettingsIcon) > .mx_TabbedView_tabLabel_text::after {
content: "🔗";
margin-left: 16px;
}
/* Settings Dialog - Tab - Manage Subscription */
.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_manageSubscriptionIcon) > .mx_TabbedView_tabLabel_text::after {
content: "🔗";
margin-left: 16px;
}
/* Settings Dialog - Appearance - Custom CSS TextArea */
.mx_AppearanceUserSettingsTab .mx_Field.mx_Field_textarea {
width: 100% !important;
height: 500px !important;
}
/**********************/
/* Inbox - Favourites */
/**********************/
/* Inbox - Favourites */
.bp_LeftPanel .rooms > .favourites {
/* Custom Variables */
--devalias-fav-section-max-height: 30vh;
--devalias-fav-grid-row-gap: 6px;
--devalias-fav-grid-col-gap: 6px;
--devalias-fav-width: 100%;
--devalias-fav-height: fit-content;
--devalias-fav-max-width: 64px;
--devalias-fav-avatar-width: 30px;
--devalias-fav-avatar-height: 30px;
--devalias-fav-avatar-font-size: 17px;
}
.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles {
max-height: var(--devalias-fav-section-max-height, none) !important;
overflow: auto;
padding-left: unset;
padding: 0;
/* Favourites Grid Spacing */
grid-row-gap: unset;
grid-gap: unset;
gap: unset;
row-gap: var(--devalias-fav-grid-row-gap, 12px);
}
.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles > .favourites__row {
height: fit-content;
/* Favourites Grid Spacing */
grid-gap: unset;
gap: unset;
column-gap: var(--devalias-fav-grid-col-gap, 12px);
}
.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles > .favourites__row > div {
height: var(--devalias-fav-height, 60px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small {
width: var(--devalias-fav-width, 100%) !important;
height: var(--devalias-fav-height, 48px) !important;
max-width: var(--devalias-fav-max-width, 100%) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .mx_BaseAvatar_image {
width: var(--devalias-fav-avatar-width, 44px) !important;
height: var(--devalias-fav-avatar-height, 44px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .mx_BaseAvatar_initial {
font-size: var(--devalias-fav-avatar-font-size, 19.8px) !important;
width: var(--devalias-fav-avatar-width, 44px) !important;
line-height: var(--devalias-fav-avatar-width, 44px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .outline {
max-width: var(--devalias-fav-max-width, 110px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .outline > span {
max-width: var(--devalias-fav-max-width, 110px) !important;
}
/************************/
/* Beeper Space Sidebar */
/************************/
/* Beeper Space Sidebar - Collapsed - Hide Floating 'Open Archive' Button */
.mx_MatrixChat > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/archive16.2003809.svg"]) {
display: none;
}
/* Beeper Space Sidebar - Show Floating 'Archive All Read Messages' Button */
.mx_MatrixChat > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
width: max-content !important;
/*position: absolute;
top: calc(38vh);
left: 15vw;*/
}
.mx_MatrixChat > div > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
display: block !important;
}
.mx_MatrixChat > div > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
opacity: unset;
}
/* Beeper Space Sidebar - Hide Original Icons */
#beeperSpaceBar svg:not(svg[data-src="img/beeper/square-inbox16.ea471fd.svg"]):not(svg[data-src="img/beeper/square-lowpriority16.6779879.svg"]):not(svg[data-src="img/beeper/square-archive16.b1ef8a0.svg"]):not(svg[data-src="img/beeper/square-bookmarks16.6853926.svg"]):not(svg[data-src="img/beeper/add-network16.1eb5cb1.svg"]) {
display: none
}
/* Beeper Space Sidebar - Use Lineart Icons */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg) > div > div > div {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
/* Beeper Space Sidebar - Use Lineart Icons - Facebook */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-facebook16.9d9e23d.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/messenger.1544eb2.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Instagram */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-instagram16.1e8ed4e.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/instagram.e9184e9.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - iMessage */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-imessage16.11b6604.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/imessage.aedae37.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Twitter */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-twitter16.ddd9bad.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/twitter.972096c.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Telegram */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-telegram16.d011ded.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/telegram.77fa320.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Signal */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-signal16.85ba0c4.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/signal.315d199.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - LinkedIn */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-linkedin16.f764edc.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/linkedin.2297fef.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Discord */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-discord16.e91cca3.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/discord.6daf490.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Beeper */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-beeper16.749ed9b.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/beeper.c685f80.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - WhatsApp */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-whatsapp16.411f722.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/whatsapp.2210499.svg");
}
/*****************/
/* Uncategorized */
/*****************/
/* Ensure full image preview is visible, rather than a zoomed part of it */
.mx_EventTile_image .mx_MImageBody img.mx_MImageBody_thumbnail {
object-fit: contain !important;
}
@kubo6472
Copy link

May I "request" an update for all the new useless buttons that bloat the screen since the latest update, please? 😅

@0xdevalias
Copy link
Author

@kubo6472 Haha, you can always ask; and if it's not too hard I can probably knock something together for you :) Though which buttons in particular are you referring to?

@kubo6472
Copy link

Heya, I didn't know what was the better way to ask, so I did both.
file_0_20230712082522

mainly hiding this new top button that "conveniently" doesn't have any class. that'd be lovely.

@0xdevalias
Copy link
Author

0xdevalias commented Jul 12, 2023

Something like this should do it:

.mx_MatrixChat > .bp_LeftPanel > .bp_Header > .controls > div:has(svg[data-src="img/beeper/question-mark16.7154382.svg"]) {
  display: none;
}

Also added to the other issue thread here:


Use 'Toggle Developer Tools' -> select the element you want to look at, then look at what things you could target in the hierarchy above it, etc; you can then test it in the devtools console with `$$('the css selector here') and see if it matches the thing you expect/want.

MDN docs are your friend:

@kubo6472
Copy link

kubo6472 commented Jul 12, 2023

This is my final version at use as of January 2024:

/******************************************************************/
/* Ensure 'report bug' dialog doesn't take over the entire screen */
/******************************************************************/
#mx_Dialog_Container div[aria-describedby="report_bug"] {
  /* Don't take up the entire screen */
  width: fit-content;
  height: fit-content;
  padding: 20px;
  border-radius: 20px;

  /* Center the dialog */
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/**********************/
/* Main Settings Menu */
/**********************/

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Chat Networks"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Mark All As Read"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Check for Update"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Download Mobile App"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Invite Friends"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Help"] {
  display: none;
}

/*******************/
/* Settings Dialog */
/*******************/

/* Settings Dialog - Tab - Chat Networks */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_chatSettingsIcon) > .mx_TabbedView_tabLabel_text::after {
  content: "🔗";
  margin-left: 16px;
}

/* Settings Dialog - Hide iMessage */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_imessageIcon) {
  display: none;
}

/* Settings Dialog - Hide Stickers */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_stickerIcon) {
  display: none;
}

/* Settings Dialog - Hide Inbox */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_inboxIcon) {
  display: none;
}

/* Settings Dialog - Hide Voice n Video */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_voiceIcon) {
  display: none;
}

/* Settings Dialog - Hide Labs */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_labsIcon) {
  display: none;
}

/* Settings Dialog - Appearance - Custom CSS TextArea */

.mx_AppearanceUserSettingsTab .mx_Field.mx_Field_textarea {
  width: 100% !important;
  height: 500px !important;
}

.mx_AppearanceUserSettingsTab .mx_Field.mx_Field_textarea textarea {
  font-family: Menlo, monospace;
  font-size: 13px !important;
}

/************************/
/* Beeper Space Sidebar */
/************************/

/* Beeper Space Sidebar - Collapsed - Hide Floating 'Open Archive' Button */

.mx_MatrixChat > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/archive16.2003809.svg"]) {
  display: none;
}

/* Beeper Space Sidebar - Show Floating 'Archive All Read Messages' Button */

.mx_MatrixChat > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
  display: none;
}
/* Beeper Space Sidebar - Hide Original Icons */

#beeperSpaceBar svg:not(svg[data-src="img/beeper/square-inbox16.ea471fd.svg"]):not(svg[data-src="img/beeper/square-gmessages16.1d4b9c9.svg"]):not(svg[data-src="img/beeper/square-googlechat16.61657b2.svg"]):not(svg[data-src="img/beeper/square-lowpriority16.6779879.svg"]):not(svg[data-src="img/beeper/square-archive16.b1ef8a0.svg"]):not(svg[data-src="img/beeper/square-bookmarks16.6853926.svg"]):not(svg[data-src="img/beeper/add-network16.1eb5cb1.svg"]) {
  display: none
}

/* Beeper Space Sidebar - Use Lineart Icons */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg) > div > div > div {
    width: 100%;
    height: 100%;
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
}

/* Beeper Space Sidebar - Use Lineart Icons - Facebook */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-facebook16.9d9e23d.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/messenger.1544eb2.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Instagram */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-instagram16.1e8ed4e.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/instagram.e9184e9.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - iMessage */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-imessage16.11b6604.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/imessage.aedae37.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Twitter */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-twitter16.ddd9bad.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/twitter.972096c.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Telegram */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-telegram16.d011ded.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/telegram.77fa320.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Signal */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-signal16.85ba0c4.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/signal.315d199.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - LinkedIn */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-linkedin16.f764edc.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/linkedin.2297fef.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Discord */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-discord16.e91cca3.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/discord.6daf490.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Beeper */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-beeper16.749ed9b.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/beeper.c685f80.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - WhatsApp */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-whatsapp16.411f722.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/whatsapp.2210499.svg");
}

/*****************/
/* Uncategorized */
/*****************/

/* Hide useless invite button */
.mx_HeaderButtons:nth-child(1) {
 display: none;
}

/* Ensure full image preview is visible, rather than a zoomed part of it */
.mx_EventTile_image .mx_MImageBody img.mx_MImageBody_thumbnail {
  object-fit: contain !important;
}

/* Hide Get Help */
.mx_MatrixChat > .bp_LeftPanel > .bp_Header > .controls > div:has(svg[data-src="img/beeper/question-mark16.7154382.svg"]) {
  display: none;
}

@0xdevalias
Copy link
Author

That worked, lovely, thank you!

@kubo6472 Glad it did :)

I did not need the counters.

@kubo6472 Mm, that's fair. They're not perfect for sure, but for me they are a CSS only hack towards getting better functionality that I would like to see on the inbox filters.

this is funny 😅

@kubo6472 Lol, yeah. Definitely an edge case not currently accounted for in the CSS hacks as is. I suspect the solution would be to also hide the 'error state' icons similar to how we hide the default icons; and then also detect when the error state should be shown and use an 'error state' lineart icon instead of the normal one in that instance.

@jorgeparamos
Copy link

jorgeparamos commented Aug 1, 2023

Amazing work @0xdevalias, thank you!

Two suggestions/requests (that may have been covered elsewher, but I couldn't find them) to achieve the effect in the mock-up:

  1. Turn the main window fully transparent.
  2. Make chat bubbles by different users have more contrasting colors.

Any help is appreciated: from sharing a full solution to just pointing me to which CSS elements are relevant. Thanks!
image

@0xdevalias
Copy link
Author

Amazing work @0xdevalias, thank you!

@jorgeparamos Glad it's helpful :)


Make chat bubbles by different users have more contrasting colors

@jorgeparamos This took a while to figure out, as it wasn't super clear where the background colours for the messages were even coming from at first..

The DOM structure of a message in a chat looks like this, with the particularly interesting bit for the colours of the message body seeming to be the <div class="mx_EventTile_line mx_EventTile_line--with-ts">:

image

Looking at the CSS styles applied here, I can see some rules in theme-dark.css that seem to impact these messages + set a background on them; and then there are some other rules that also seem to match for the replies part of things:

.mx_EventTile_messageIn .mx_EventTile_line
.mx_EventTile_messageIn.mx_EventTile_lastInSection .mx_EventTile_line:before
.mx_EventTile_messageIn .mx_ReplyChain
.mx_ReplyChain

From a quick 'playing around', it seems like toggling off the backgrounds in all of those rules removes them from the chat:

image

If we then write some rules like this, we can set all of the bits of the backgrounds to the colours we choose (there seems to be the 'main bubble', the 'bubble tail', and then the 'reply section':

.mx_RoomView_MessageList .mx_EventTile_messageIn .mx_EventTile_line {
    background-color: red;
}

.mx_RoomView_MessageList .mx_EventTile_messageIn.mx_EventTile_lastInSection .mx_EventTile_line:before {
    background-color: green;
}

.mx_RoomView_MessageList .mx_EventTile_messageIn .mx_ReplyChain {
    background-color: blue;
}

image

Looking a little closer at the DOM structure and the CSS classes applied to it, the following look interesting/potentially useful:

.mx_EventTile_message.mx_EventTile_firstInGroup
.mx_EventTile_message.mx_EventTile_continuation
.mx_EventTile_message.mx_EventTile_lastInGroup
.mx_EventTile_message.mx_EventTile_lastInSection

If we were wanting to target/set a colour for a specific username, it seems we could write a CSS selector that matches against the username in the title attr of the profile picture with something like this (not that that is necessarily helpful to the usecase here, but figured i'd note it as I explored things):

$$('.mx_EventTile_message .mx_MessageBody .mx_EventTile_avatar .mx_BaseAvatar_image[title="HReflex"]')

My first thought was that maybe we could do something tricky using CSS variables, but the easiest way for that to work would have been if all of the 'grouped messages' of a user had the same parent element we could target, but it seems that's not the case:

My next thought is that perhaps we could do something tricky using CSS counters somehow, though not exactly sure what that would be/how it would work exactly (if it's even possible); and even if we do manage to make it work, I'm not sure that it would easily allow us to always keep the same colour mapped to the same user:

The high level of my thought with CSS counters is that maybe we could have it set a colour value at the start of a group of messages, use that throughout those messages, and then have it reset at the end of that group (or at the start of the next one); but I'm not even sure if that is actually possible.

This is the type of thing that would be almost trivial to achieve with JavaScript/similar; but through CSS alone might be super challenging if not impossible unfortunately.


Turn the main window fully transparent

@jorgeparamos This isn't something I'm deeply familiar with, but I found the following resources for how to achieve this with an electron app (which is what Beeper is built on):

Based on that, the short answer is that it relies on being able to change JavaScript in some of the core internals of Beeper/Electron, so I believe that it's basically not possible to do with just CSS changes alone.

If we start Beeper with remote debugging enabled (eg. /Applications/Beeper.app/Contents/MacOS/Beeper --inspect-brk=1337), then connect to it from Chrome DevTools (chrome://inspect), then we can search the source for BrowserWindow, which we find in electron-main.js. There are 2 main instances of this:

  • The first is seemingly used for some bridge auth related things
    • image
  • Whereas the second looks to be the main window we interact with
    • image

Looking at the options passed to that 2nd BrowserWindow, we can see that it sets frame as frame: argv['default-frame'] ?? false, implying that it might be possible to influence this via command line arguments; but there doesn't seem to be a transparent option defined at all.

Even if I hack the source to enable transparent in the debugger, and then manually crawl through the various DOM nodes with background colours and set them to transparent as well, it still seems to give me a blurred/'frosted' looking transparent, rather than purely transparent (which weirdly seems to become opaque again when I don't have the Beeper window selected):

image

I'll also note that there are these settings in the standard Beeper settings, that give some transparency, but also not the full transparency you seem to be after:

image

Though looking at the code, they might only be shown to Mac users for some reason (even though apparently electron for windows can handle transparency as well):

image

Looking in theme-dark.css via Beeper's DevTools, there seem to be a lot of CSS variables that could be overridden to tweak things. I haven't looked too deeply at them, but the 'Transparency - Chat View' settings toggle seems to interact with the --main-panel-transparent-bg CSS variable; so that might potentially be an area worth looking into more:

image

There may be some other CSS properties interacting with this 'frosted'/'blurred' transparency, such as background-blend-mode or blur or otherwise.. or it might just be a limitation in how electron does things in general. Hard to say for sure without diving deeper into things:

@0xdevalias
Copy link
Author

@jorgeparamos Just tried out a basic test of my thoughts/theory with using the CSS counters to change background colours, and it seems like it wouldn't work:

@jorgeparamos
Copy link

@jorgeparamos Just tried out a basic test of my thoughts/theory with using the CSS counters to change background colours, and it seems like it wouldn't work:

Whoa! Thanks a lot for all the hints and effort put into it: I understood half and managed to implement another half of that, and attained that translucid (i.e. sort of transparent, but blurred) look — which I find still benefits the app a lot.

Thanks again @0xdevalias!

@0xdevalias
Copy link
Author

I understood half and managed to implement another half of that, and attained that translucid (i.e. sort of transparent, but blurred) look — which I find still benefits the app a lot.

Thanks again @0xdevalias!

@jorgeparamos Awesome, glad you could get some of it figured :) If you're open to sharing the CSS hacks you ended up with here they might be useful for someone else in future too 🖤

@evkaw
Copy link

evkaw commented Oct 10, 2023

Someone please help me get rid of the "invite to beeper" button in chats, I don't know how to use CSS nor how to get into the Inspect tool.. Thanks

@0xdevalias
Copy link
Author

Someone please help me get rid of the "invite to beeper" button in chats, I don't know how to use CSS nor how to get into the Inspect tool.. Thanks

@evkaw On Beeper Desktop, go to the menu bar -> View -> Toggle Developer Tools; then from the devtools you can select the 'Elements' tab, and then in the top left you should find the 'inspect element' tool (Shortcut seems to be apple+shift+c, or whatever the windows equivalent of that is)

Something like this would probably do it:

.bp_MainPanel > .mx_RoomView > .mx_RoomHeader .mx_RoomHeader_rightActions .mx_RoomHeader_rightActions_static > :nth-child(1 of .mx_HeaderButtons) {
  display: none;
}

@FaviFake
Copy link

FaviFake commented Nov 6, 2023

Hey,

I don't know why, but Beeper seems to be the only messaging app that uses a much smaller font for the chat list compared to all other UI elements. Also, the message previews occupy two rows instead of just one. I'd love if you could make the text used in the chat list as big as the one used in the message bubbles, and, if possible, also remove the second row in the preview. I have no idea how CSS works, so idk if what I'm asking is very hard or incredibly simple. I've made a badly-photoshopped concept to show how it could look:

Before

image

After

1699185663659

@jackmawer
Copy link

@jorgeparamos Just tried out a basic test of my thoughts/theory with using the CSS counters to change background colours, and it seems like it wouldn't work:

Whoa! Thanks a lot for all the hints and effort put into it: I understood half and managed to implement another half of that, and attained that translucid (i.e. sort of transparent, but blurred) look — which I find still benefits the app a lot.

Thanks again @0xdevalias!

Would love to know how you ended up achieving this!

@aronaoi
Copy link

aronaoi commented Dec 27, 2023

Is there any way to make Beeper Cloud act like a mobile app? I want to be able to shrink the window size to like 350 pixels, and it will display the chat list when i am not on an open chat, and clicking on a chat will bring me to the chat, and i can go back to the chat menu with a back button (basically, act the same way as Beeper Cloud mobile)

@0xdevalias
Copy link
Author

I want to be able to shrink the window size to like 350 pixels, and it will display the chat list when i am not on an open chat, and clicking on a chat will bring me to the chat, and i can go back to the chat menu with a back button (basically, act the same way as Beeper Cloud mobile)

@aronaoi Does this get you close to what you want?

@aronaoi
Copy link

aronaoi commented Dec 28, 2023

I want to be able to shrink the window size to like 350 pixels, and it will display the chat list when i am not on an open chat, and clicking on a chat will bring me to the chat, and i can go back to the chat menu with a back button (basically, act the same way as Beeper Cloud mobile)

@aronaoi Does this get you close to what you want?

Sorta. My issue is that I cannot resize the window size narrower. I want it to only display the Chat List when no chat is selected, and when clicking on a chat it will bring me to the chat messages like how Beeper Cloud on iPhone / Android works. Let’s say the window size minimum pixel is locked at 600 px but i want to be able to go even smaller like 300 px.

With the addition of a back button (like the mobile ver) if possible <3

@0xdevalias
Copy link
Author

My issue is that I cannot resize the window size narrower.

@aronaoi Did you also see the comment after that about changing the min-width on the left side panel? While it won't solve your issue completely, it might help a little?


I want it to only display the Chat List when no chat is selected, and when clicking on a chat it will bring me to the chat messages like how Beeper Cloud on iPhone / Android works.

@aronaoi The link I shared earlier (Ref) will hide the 'chat list' when a chat is selected, but it won't hide the 'main chat area' when none is selected; so some more work would need to be done on it if that was your desired outcome (though not even sure if it's possible, see below)


Let’s say the window size minimum pixel is locked at 600 px but i want to be able to go even smaller like 300 px.

@aronaoi From a quick ChatGPT/Google, it sounds like for an Electron app (which Beeper is), the minimum width of the window is set in the BrowserWindowConstructorOptions that are passed to the BrowserWindow when it is set up:

Unfortunately this lives on the 'Electron' side of things, and isn't something we are able to manipulate through CSS on the 'app' side of things. To do that, you would need to modify the relevant *.asar files, or perhaps inject some custom JS into Beeper at startup to be able to modify this; both of which are more involved than the typical CSS only changes here.

If you wanted to look deeper into doing this, you might find some useful notes/snippets on some of my gists, eg.:

By starting Beeper in Electron debug mode, and connecting to it from chrome://inspect, we can see the main BrowserWindow config Beeper is using; and how it has minWidth set to 660:

file:///Applications/Beeper.app/Contents/Resources/app.asar/lib/electron-main.js:

// app.asar/lib/electron-main.js; Lines 1282-1315
// ..snip..
const preloadScript = path_1.default.normalize(`${__dirname}/preload.js`);
    mainWindow = global.mainWindow = new electron_1.BrowserWindow({
        // https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
        ...(isMac ? {} : { backgroundColor: "#fff" }),
        icon: iconPath,
        show: false,
        autoHideMenuBar: store.get("autoHideMenuBar", true),
        x: mainWindowState.x,
        y: mainWindowState.y,
        minWidth: 660,
        minHeight: 400,
        width: mainWindowState.width,
        height: mainWindowState.height,
        frame: argv["default-frame"] ?? false,
        titleBarStyle: isMac ? "hidden" : null,
        trafficLightPosition: { x: 16, y: 20 },
        webPreferences: {
            //devTools: false,
            //zoomFactor: 0.9,
            preload: preloadScript,
            nodeIntegration: true,
            //sandbox: true, // We enable sandboxing from app.enableSandbox() above
            // We don't use this: it's useful for the preload script to
            // share a context with the main page so we can give select
            // objects to the main page. The sandbox option isolates the
            // main page from the background script.
            spellcheck: true,
            contextIsolation: true,
            webgl: true,
            backgroundThrottling: false,
        },
        vibrancy: "fullscreen-ui",
        visualEffectState: "followWindow",
    });
// ..snip..

With the addition of a back button (like the mobile ver) if possible

@aronaoi Unfortunately I don't think that one will be possible with CSS alone (or at least, doing so is beyond my current skills/knowledge with it), so if you wanted to do that, it would also likely need to also be a JS hack/injection like I mentioned above.

@0xdevalias
Copy link
Author

0xdevalias commented Dec 29, 2023

RE: https://gist.github.com/0xdevalias/3d2f5a861335cc1277b21a29d1285cfe?permalink_comment_id=4808978#gistcomment-4808978

Would love to know how you ended up achieving this!

@jackmawer Which particular part are you referring to by 'this'?

Do you mean this part, from @jorgeparamos (Ref):

I understood half and managed to implement another half of that, and attained that translucid (i.e. sort of transparent, but blurred) look — which I find still benefits the app a lot.

@mary-ext
Copy link

mary-ext commented Mar 27, 2025

Ctrl+Shift+I doesn't seem to open devtools on v4 anymore, it opens up chat details pane now.

@0xdevalias
Copy link
Author

Ctrl+Shift+I doesn't seem to open devtools on v4 anymore

@mary-ext That's frustrating :(

image

I tried toggling it off and reloading the app, but even then it still didn't seem to allow me to open devtools :'(

I just reported the following feedback to Beeper based on this:

In the old version of beeper desktop we could access the electron devtools from the menu, or from a keyboard shortcut like Command+Shift+I.

In earlier versions of desktop v4 the menu item was gone, but we could still use Command+Shift+I, but now we can't even use that because it's seemingly overridden by 'Toggle Chat Info'.

Ideally we would have access to be able to open electron devtools via the application menu, and probably from the Command+J action interface as well.

It might also make sense to put a button for it near the custom CSS section in the settings, since devtools is one of the most common and easily used tools for inspecting what we need to target for CSS customisations.

@0xdevalias
Copy link
Author

0xdevalias commented Mar 29, 2025

Ctrl+Shift+I doesn't seem to open devtools on v4 anymore

@mary-ext So on Windows, if you disable that shortcut toggle, then it should work again (a few others confirmed this), and on macOS, apparently the shortcut is Command+Option+I, which is why it wasn't working for me above 😅

I've updated my notes above to mention both shortcuts + the note on disabling the keybinding:

@mary-ext
Copy link

oh, awesome! didn't realize I could just disable that keyboard shortcut

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment