Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save juanbrujo/72c45057b104bd5986ebe54d1241dce5 to your computer and use it in GitHub Desktop.

Select an option

Save juanbrujo/72c45057b104bd5986ebe54d1241dce5 to your computer and use it in GitHub Desktop.
YouTube - Hide Live Chat By Default
// ==UserScript==
// @name YouTube - Hide Live Chat By Default
// @namespace https://gist.github.com/lbmaian/94824cef728917a53d3c6e6ea885469c
// @downloadURL https://gist.github.com/lbmaian/94824cef728917a53d3c6e6ea885469c/raw/youtube-hide-livechat.user.js
// @updateURL https://gist.github.com/lbmaian/94824cef728917a53d3c6e6ea885469c/raw/youtube-hide-livechat.user.js
// @version 0.14
// @description Hide live chat by default on live streams
// @author lbmaian
// @match https://www.youtube.com/*
// @exclude https://www.youtube.com/embed/*
// @icon https://www.youtube.com/favicon.ico
// @run-at document-start
// @grant none
// ==/UserScript==
(function() {
'use strict';
const DEBUG = false;
const logContext = '[YouTube - Hide Live Chat]';
var debug;
if (DEBUG) {
debug = function(...args) {
console.debug(logContext, ...args);
}
} else {
debug = function(...args) {}
}
function log(...args) {
console.log(logContext, ...args);
}
function warn(...args) {
console.warn(logContext, ...args);
}
function error(...args) {
console.error(logContext, ...args);
}
// Note: Following all relies on YT internals.
function updateChatData(data, collapsed) {
if (DEBUG) {
debug('data (before)', window.structuredClone(data));
}
const liveChatRenderer = data.liveChatRenderer;
if (liveChatRenderer) { // if no live chat despite #chat existing, e.g. "Live chat replay is not available for this video."
const expandedByDefault = liveChatRenderer.initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_EXPANDED';
if (expandedByDefault && collapsed) {
if (collapsed) {
log('hiding live chat');
}
debug('data.liveChatRenderer.initialDisplayState:', liveChatRenderer.initialDisplayState,
'=>', 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED');
liveChatRenderer.initialDisplayState = 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED';
}
const toggleButtonRenderer = liveChatRenderer.showHideButton?.toggleButtonRenderer;
if (toggleButtonRenderer) {
if (expandedByDefault) {
debug('data.liveChatRenderer.showHideButton.toggleButtonRenderer.defaultText/toggledText swapped');
[toggleButtonRenderer.defaultText, toggleButtonRenderer.toggledText] =
[toggleButtonRenderer.toggledText, toggleButtonRenderer.defaultText];
}
const isToggled = !collapsed;
if (DEBUG && toggleButtonRenderer.isToggled !== isToggled) {
debug('data.liveChatRenderer.showHideButton.toggleButtonRenderer.isToggled', toggleButtonRenderer.isToggled,
'=>', isToggled);
}
toggleButtonRenderer.isToggled = isToggled;
}
if (DEBUG) {
debug('data (updated)', window.structuredClone(data));
}
return expandedByDefault;
} else {
return false;
}
}
// Navigating to YouTube watch page can happen via AJAX rather than new page load.
// We can monitor this with YT's custom yt-page-data-fetched event,
// which conveniently also fires even for new/refreshed pages.
// yt-navigate-finish would also work (evt.detail.detail) but yt-page-data-fetched fires earlier.
document.addEventListener('yt-page-data-fetched', evt => {
debug('Navigated to', evt.detail.pageData.url);
debug(evt);
const conversationBar = evt.detail.pageData.response?.contents?.twoColumnWatchNextResults?.conversationBar;
debug('yt-page-data-fetched pageData.response contents.twoColumnWatchNextResults.conversationBar (corresponds to #chat.data)',
conversationBar);
// If response doesn't include conversationBar, there won't be a #chat element at all.
if (conversationBar) {
// If #chat element isn't created yet, default collapsed to true.
// Else keep current collapsed status between pages.
// TODO: sometimes for new pages when chat doesn't exist yet, this apparently happens too late?
// (chat already initialized with old data) and chat thus remains open?
// Detect & fix this - use chat.parentComponent (ytd-watch-flexy)'s updatePageData_ or ytd-app's onYtPageDataFetched?
const chat = document.getElementById('chat');
let collapsed;
if (chat) {
collapsed = chat.collapsed;
log('existing #chat', chat, 'collapsed:', collapsed);
} else {
log('no existing #chat, defaulting collapsed: true');
collapsed = true;
}
updateChatData(conversationBar, collapsed);
}
});
})();
@flixerto
Copy link
Copy Markdown

This userscript is a clean and efficient solution for improving focus during YouTube live streams by automatically hiding the live chat by default. Instead of removing the chat entirely, it smartly modifies YouTube’s internal page data so the chat loads in a collapsed state, reducing distractions while still keeping it accessible when needed. By working directly with YouTube’s navigation events and UI state, it ensures the setting applies consistently across both refreshed and dynamically loaded pages.

@wcofun
Copy link
Copy Markdown

wcofun commented May 9, 2026

This userscript is a clever and practical quality-of-life tweak for anyone who regularly watches live streams on YouTube. Instead of relying on manual clicks every time a stream loads, it automatically collapses the live chat by modifying YouTube’s internal page data before the interface fully renders. That’s a smart approach—it makes the change feel native rather than forced after the fact. Visit

@gmoviestv
Copy link
Copy Markdown

This userscript is a smart quality-of-life upgrade for YouTube live stream viewers who prefer focus over noise. Instead of simply hiding chat after the page loads, it cleverly Punisher hooks into YouTube’s internal page data system to collapse live chat before it becomes a distraction—even during AJAX navigation between videos.

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