Allows any number of items per row, both full-length and shorts, on the subscription page.
Custom Subscription Items Per Row by Xcogitaire
Details
AuthorXcogitaire
Licensehttps://creativecommons.org/licenses/by/4.0/
Categoryyoutube.com
Created
Updated
Code size9.2 kB
Code checksum60596dbc
Statistics
Learn how we calculate statistics in the FAQ.
Failed to fetch stats.
Description
Notes
Requires Firefox 121, Chrome 105, Edge 105, or equivalent; uses the :has selector.
To change the number of items per row, change the two custom properties at the top of the script, then set the literal numbers inside the nth-child
s later in the script (as described in their rule's comments).
Source code
/* ==UserStyle==
@name Custom Subscription Items Per Row
@namespace github.com/openstyles/stylus
@version 1.1.1
@description Allows any number of items per row, both full-length and shorts, on the subscription page.
@author Xcogitaire
@license https://creativecommons.org/licenses/by/4.0/
==/UserStyle== */
@-moz-document url-prefix("https://www.youtube.com/feed/subscriptions")
{
/* Give the outermost video container actual padding */
#primary > [is-default-grid] > #contents
{
box-sizing: border-box;
padding-left: var(--ytd-rich-grid-gutter-margin);
padding-right: var(--ytd-rich-grid-gutter-margin);
/*
[ Defaults: 4 and 6, as of April 27th 2025 ]
[ Be sure to update the `nth-child`s below, they can't be made dynamic (as far as I know/have researched)! ]
Max is 25, assuming shorts comes after row 2; any more, and the grid will be displayed in
a funky order/not be complete.
To increase the max, add more to the section below that manually gives each item an
index (no, there isn't a way to do it dynamically, as far as I know/can find).
If you actually do want to do that, note Stylus's code editor allows multiple cursors
if you ctrl+click. It's not as strong as VSCode's multi-cursor, but it's something!
*/
--ytd-rich-grid-items-per-row: 6;
--ytd-rich-grid-slim-items-per-row: 8;
/* After what row should shorts be ordered? Default is 2. */
--shorts-row-number: 2;
/*
Smaller number of items per row for smaller screen sizes
Note: Firefox's mobile simulator never seems to say the document is thinner than 980px, even when
it definitely is. This may have something to do with youtube having a weird <meta> in its <head>,
in which case, media queries with max-width below 980px might be unreachable.
*/
@media (max-width: 1620px)
{
--ytd-rich-grid-items-per-row: 4;
--ytd-rich-grid-slim-items-per-row: 6;
}
@media (max-width: 980px)
{
--ytd-rich-grid-items-per-row: 2;
--ytd-rich-grid-slim-items-per-row: 4;
}
}
/*
Show shorts that would be visible with this new items per row var, that currently aren't
** Make the literal numbers in `nth-child` equal to the values of `--ytd-rich-grid-slim-items-per-row` above **
Yes, the `:not`s are kind of silly, it could just be iPR - 1, but I want to minimize confusion.
*/
[is-shorts] ytd-rich-item-renderer[is-slim-media]:nth-child(-n + 8)
{
/* Disable this with ctrl+/ or whatever if you don't wanna update these numbers/would just like the shorts centered */
display: block !important;
@media (max-width: 1620px)
{
/* change ──────┬─────────────────┐ */
&:nth-child(n + 6):not(:nth-child(6))
{
display: none !important;
}
}
@media (max-width: 980px)
{
/* change ──────┬─────────────────┐ */
&:nth-child(n + 4):not(:nth-child(4))
{
display: none !important;
}
}
}
/* Optionally center shorts contents (should only relevant if you turn the above off, or don't update the nth-childs) */
[is-shorts] #contents-container > #contents
{
/* These transitions are only a formality, `auto` can't be animated (yet--seems like that's changing soonish, relatively) */
transition-property: height, margin;
& > :first-child
{
transition: margin .2s ease-in-out;
margin-left: auto;
}
& > :nth-last-child(-n + 1 of :not([hidden]))
{
transition: margin .2s ease-in-out;
margin-right: auto;
}
}
/* Cancel out the fake-ish padding the child elements enforce, sicne we added proper padding to their parent */
#primary > [is-default-grid] > #contents > ytd-rich-section-renderer
{
box-sizing: border-box;
margin-left: calc(var(--ytd-rich-grid-gutter-margin) * -1);
margin-right: calc(var(--ytd-rich-grid-gutter-margin) * -1);
width: calc(100% + var(--ytd-rich-grid-gutter-margin) * 2);
}
ytd-rich-item-renderer[rendered-from-rich-grid]
{
/*
This is the same as the native `ytd-rich-item-renderer` style, which is overridden by another native style with the
same selector as this, replacing the items per row var with with another that takes the left and right gutters into
account. Naturally, since we added some actual, proper left and right padding, we don't need or want that.
The `- 0.01` is probably for ensuring the number of items per row/preventing items from wrapping prematurely. This
appears to be unnecessary, but it doesn't change much, and can't hurt to leave in.
*/
width: calc(100%/var(--ytd-rich-grid-items-per-row) - var(--ytd-rich-grid-item-margin) - .01px);
/* For similar reasons, remove extra margin from "first in row" items. */
&[is-in-first-column]
{
margin-left: calc(var(--ytd-rich-grid-item-margin) / 2);
}
}
/* #region Give each item an index (collapse this section with arrow left of bracket as desired) */
#primary > [is-default-grid] > #contents ytd-rich-item-renderer
{
&:nth-of-type(1) { --index: 1; }
&:nth-of-type(2) { --index: 2; }
&:nth-of-type(3) { --index: 3; }
&:nth-of-type(4) { --index: 4; }
&:nth-of-type(5) { --index: 5; }
&:nth-of-type(6) { --index: 6; }
&:nth-of-type(7) { --index: 7; }
&:nth-of-type(8) { --index: 8; }
&:nth-of-type(9) { --index: 9; }
&:nth-of-type(10) { --index: 10; }
&:nth-of-type(11) { --index: 11; }
&:nth-of-type(12) { --index: 12; }
&:nth-of-type(13) { --index: 13; }
&:nth-of-type(14) { --index: 14; }
&:nth-of-type(15) { --index: 15; }
&:nth-of-type(16) { --index: 16; }
&:nth-of-type(17) { --index: 17; }
&:nth-of-type(18) { --index: 18; }
&:nth-of-type(19) { --index: 19; }
&:nth-of-type(20) { --index: 20; }
&:nth-of-type(21) { --index: 21; }
&:nth-of-type(22) { --index: 22; }
&:nth-of-type(23) { --index: 23; }
&:nth-of-type(24) { --index: 24; }
&:nth-of-type(25) { --index: 25; }
&:nth-of-type(26) { --index: 26; }
&:nth-of-type(27) { --index: 27; }
&:nth-of-type(28) { --index: 28; }
&:nth-of-type(29) { --index: 29; }
&:nth-of-type(30) { --index: 30; }
&:nth-of-type(31) { --index: 31; }
&:nth-of-type(32) { --index: 32; }
&:nth-of-type(34) { --index: 34; }
&:nth-of-type(35) { --index: 35; }
&:nth-of-type(36) { --index: 36; }
&:nth-of-type(37) { --index: 37; }
&:nth-of-type(38) { --index: 38; }
&:nth-of-type(39) { --index: 39; }
&:nth-of-type(40) { --index: 40; }
&:nth-of-type(41) { --index: 41; }
&:nth-of-type(42) { --index: 42; }
&:nth-of-type(43) { --index: 43; }
&:nth-of-type(44) { --index: 44; }
&:nth-of-type(45) { --index: 45; }
&:nth-of-type(46) { --index: 46; }
&:nth-of-type(47) { --index: 47; }
&:nth-of-type(48) { --index: 48; }
&:nth-of-type(49) { --index: 49; }
&:nth-of-type(50) { --index: 50; }
}
/* #endregion */
/* Finally, move videos under the shorts area that should now be above it, above it */
#primary > [is-default-grid] > #contents > *
{
/*
Order by index up to the shorts row, then clamp to that number + 1 (there's no need for any further order edits)
First we store the breakpoint index for reuse, then we use `--index` to determine if this item is past that point;
if index - bpIndex is non-zero positive, the answer is yes. Result clamped to 0 or 1align-content
Then, when we actually apply the order, take the index or the bpIndex, whichever's smaller, or bpIndex if no index.
Finally, add 2 if we're past the breakpoint; this leaves room for the shorts section itself, which will fit into
the left over `bpIndex + 1` slot.
This ensures, no matter the difference between custom/native items per row, that each pre-breakpoint row will
display before the shorts section, *immediately* followed by the shorts section, THEN everything else.
*/
--shorts-breakpoint-index: calc(var(--ytd-rich-grid-items-per-row) * var(--shorts-row-number));
--beyond-breakpoint-01: max(min(calc(var(--index, infinity) - var(--shorts-breakpoint-index)), 1), 0);
order: calc(min(var(--index, infinity), var(--shorts-breakpoint-index)) + var(--beyond-breakpoint-01) * 2);
&:first-child
{
order: 0;
}
&:has([is-shorts])
{
order: calc(var(--shorts-breakpoint-index) + 1);
}
}
}