Skip to content

Instantly share code, notes, and snippets.

@christippett
Last active March 11, 2025 04:02
Show Gist options
  • Save christippett/5097af0ea59c867c4578996350933776 to your computer and use it in GitHub Desktop.
Save christippett/5097af0ea59c867c4578996350933776 to your computer and use it in GitHub Desktop.
Hacker News Stylesheet

Hacker News userstyle

A modern re-styling of the Hacker News front page, including automatic light/dark mode.

Install directly with Stylus

Screenshots

HN 1

HN 2

/* ==UserStyle==
@name Hacker's New CSS
@author Chris Tippett <[email protected]> (https://christippett.dev)
@description A modern retake on the classic Hacker News design.
@version 2024.08.12
@namespace @christippett
@homepageURL https://github.com/christippett
@supportURL https://gist.github.com/christippett/5097af0ea59c867c4578996350933776/
@updateURL https://gist.github.com/christippett/5097af0ea59c867c4578996350933776/raw/hn.user.css
@license MIT
@preprocessor uso
==/UserStyle== */
@-moz-document domain("news.ycombinator.com") {
:root {
color-scheme: light dark;
}
html {
--font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
--primary: #ff6600;
--primary-light: rgb(246, 125, 50);
--primary-alt: #c8c5c5;
--text-color: #4b4b4b;
--text-color-alt: #828282;
--table-bg: #fcfffa;
--body-bg: #f6f6ef;
--border-color: #f6f6ef;
--border-radius: 3px;
--smaller: 0.85em;
--content: 0.96rem;
--magic-num: 0.4rem;
font-size: 14px;
color: var(--text-color);
}
@media (prefers-color-scheme: dark) {
html {
--text-color: #f0f0f0;
--text-color-alt: #c3c3c3;
--primary-alt: #828282;
--border-color: #1b1b1c;
--table-bg: #232323;
--body-bg: #1b1b1c;
}
}
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-border-horizontal-spacing: 0;
-webkit-border-vertical-spacing: 0;
}
body {
padding: 20px 40px 50px;
background-color: var(--body-bg);
}
body,
td,
input[type="submit"],
.default,
.admin,
.title,
.subtext,
.yclinks,
.pagetop,
.comhead,
.comment {
font-family: var(--font-family);
}
code {
color: var(--primary-light);
}
@media (max-width: 750px) {
body {
padding: 0;
}
}
body>center {
position: relative;
}
table {
width: 100%;
}
td,
td.default,
.admin td {
font-size: var(--content);
color: var(--text-color);
line-height: 1.4;
}
p,
.default p,
.admin p {
margin-top: var(--smaller);
margin-bottom: var(--smaller);
}
br {
padding: 0;
}
/* hide consecutive <br> tags */
br+br,
span+br:first-of-type {
display: none;
}
font[size] {
font-size: var(--smaller);
}
/* help text on profile & post submission pages */
font[size="2"],
html[op="submit"] form>table tr:last-child td {
font-size: var(--smaller);
color: var(--text-color-alt);
font-style: italic;
}
a,
a:link,
.c00,
.c00 a:link {
color: var(--text-color);
text-underline-offset: 0.12em;
text-decoration-thickness: 1px;
}
form {
padding: 0;
max-width: 700px;
}
input[type="submit"] {
margin-top: var(--magic-num);
}
input[name="q"] {
background: var(--table-bg);
}
textarea,
input,
a.morelink {
max-width: 90%;
color: var(--text-color);
font-size: max(var(--smaller), calc(var(--magic-num) * 2));
background-color: var(--body-bg);
outline: 1px solid var(--border-color);
border: none;
border-radius: var(--border-radius);
box-shadow:
inset 0 0 1px var(--text-color),
2px 2px 4px var(--border-color);
padding: calc(var(--smaller) / 2);
}
textarea+a {
padding: var(--magic-num);
}
/* button style */
input[type="submit"],
select,
a.morelink,
.reply a {
cursor: pointer;
text-transform: capitalize;
}
.reply a:link {
color: var(--text-color-alt);
background-color: color-mix(in srgb, var(--body-bg) 50%, rgba(0, 0, 0, 0));
border-radius: var(--border-radius);
font-size: 0.85em;
padding: 0.3em 0.5em;
transition: background-color 120ms ease;
&:before {
content: "+ ";
display: inline;
}
&:hover {
background-color: var(--body-bg);
}
}
td[style="overflow:hidden"] {
overflow: visible !important;
}
/* --- LOGIN / CREATE ACCOUNT --*/
html:not([op]) body {
background-color: var(--table-bg);
}
html:not([op]) b {
display: inline-block;
margin-top: 1rem;
}
a[href="forgot"] {
display: inline-block;
margin-top: -1rem;
padding: calc(var(--magic-num) * 2);
color: var(--text-color-alt) !important;
font-style: italic;
text-decoration: none;
font-size: var(--smaller);
}
/* --- USER PROFILE --- */
#pagespace+tr>td:empty {
display: none;
}
table td:first-child:not([class])[valign="top"]+td {
padding: 0.25rem;
width: 100%;
}
/* field labels */
table td:first-child:not([class])[valign="top"] {
width: auto;
min-width: 120px;
padding-right: 1rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
}
/* --- TABLE / LAYOUT --- */
#hnmain {
max-width: 900px;
overflow: hidden;
background: var(--table-bg);
border-radius: var(--border-radius);
box-shadow: 0 0 1px var(--primary-alt);
}
/* only add padding for parent table cells */
#hnmain>tbody>tr:not([id])>td:not([bgcolor]) {
padding: 1rem 1.25rem;
width: 100%;
}
/* add left/right padding when viewing user's threads */
#hnmain>tbody>tr.athing.comtr[id]>td {
padding: 0 1.25rem;
}
#hnmain tr:not(.spacer):empty {
display: none;
}
.morespace {
display: none;
}
.morespace+tr td {
padding: var(--magic-num) 0;
}
.spacer {
height: 1rem !important;
}
/* error message shown if not logged in when making comment */
html[op="x"] #hnmain>tbody>tr:last-child td {
color: transparent;
}
/* --- HEADER --- */
td[bgcolor] {
padding: var(--magic-num);
}
#hnmain>tbody>tr:first-child>td:first-child>table td {
height: auto !important;
line-height: 1 !important;
vertical-align: middle;
}
.pagetop {
color: white;
line-height: 1;
font-weight: 500;
font-size: var(--smaller);
padding: 0 var(--magic-num);
}
.pagetop font {
font-weight: bold;
}
.pagetop :is(a, a:visited) {
color: white;
line-height: 1rem;
}
.pagetop a:hover {
text-decoration: underline;
text-decoration-color: white;
}
/* 'hacker news' title */
.hnname {
margin-right: var(--magic-num);
font-size: 1.1rem;
}
.hnname a:hover {
text-decoration: none !important;
}
#me {
font-weight: 600;
font-style: italic;
}
#karma {
font-size: var(--smaller);
line-height: 1rem;
display: inline-flex;
align-items: center;
cursor: grabbing;
}
/* can't remember what this was supposed to hide */
span:not([class])[id]:not(#karma) {
display: none;
}
/* ---- FRONT PAGE ---- */
/* stories from [Month] [Day], [Year] */
#pagespace+tr>td>div {
padding: 0 0 1rem;
margin: 0 0 1rem !important;
border-bottom: 1px solid var(--primary-alt);
font-size: var(--content);
font-weight: 500;
}
#pagespace+tr>td>div>div {
font-style: italic;
font-size: var(--smaller);
margin-top: 0.25rem !important;
}
.hnmore a,
.hnmore a:is(:link, :visited) {
color: var(--text-color-alt);
}
/* --- ARTICLE --- */
.rank {
color: var(--text-color-alt);
font-weight: 500;
}
.title {
font-size: 1rem;
overflow: unset;
}
.titleline {
width: 100%;
font-weight: 500;
display: flex;
gap: 1rem;
justify-content: flex-start;
align-items: baseline;
}
html[op="item"] .titleline>a {
font-size: 1.2rem;
font-weight: 700;
}
/* transparent parantheses around url */
.titleline .sitebit {
flex: 1;
text-align: right;
color: transparent;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
padding: 0 var(--magic-num);
}
.titleline .sitebit a {
font-weight: 300;
pointer-events: auto;
transition: color 220ms ease;
&:not(:active):hover {
color: var(--text-color);
}
}
.titleline:hover .sitebit a {
color: var(--text-color);
}
.subtext,
.subtext a {
display: inline-block;
color: var(--text-color-alt);
font-size: 0.9rem;
padding: 2px 0;
}
.subtext {
cursor: default;
}
.subtext .score {
color: var(--text-color-alt);
font-weight: 500;
border-radius: var(--border-radius);
}
/* ---- COMMENTS ---- */
table.itemlist,
table.fatitem,
table.comment-tree {
position: relative;
}
html:not([op="reply"]) table.fatitem {
padding-bottom: 0.75rem;
margin-bottom: 0.75rem;
border-bottom: 1px solid var(--primary-alt);
}
td.default {
width: 100%;
position: relative;
}
.toptext {
font-size: var(--content);
margin-bottom: -1rem;
}
/* comment: header/author/etc */
.comhead {
font-size: var(--smaller);
color: var(--text-color-alt);
}
a.hnuser {
font-weight: 500;
}
.comment {
margin: 0 0 var(--magic-num);
padding: var(--magic-num);
font-size: inherit;
}
.reply p {
margin-top: var(--smaller);
margin-bottom: var(--smaller);
}
.reply :is(u, a),
.reply a:is(:link, :visited) {
text-decoration: none;
}
.navs {
margin-left: 0.25ch;
display: inline-flex;
align-items: center;
gap: 0.75ch;
}
a.togg {
padding: 0.1em 0.2em;
font-weight: bold;
border-radius: var(--border-radius);
}
/* --- UPVOTE/DOWNVOTE LINKS --- */
.votelinks center:not(:has(font)) {
opacity: 0;
margin-top: 2px;
transition: opacity 120ms ease;
}
tr.athing:hover .votelinks center:has(a.clicky:not(.nosee)) {
opacity: 1;
}
/* self-post indicator */
.votelinks center font {
width: 1em;
display: block;
color: transparent;
position: relative;
}
.votelinks center font:before {
content: "β˜…";
color: var(--primary);
display: inline;
font-size: 0.7em;
position: absolute;
}
/* space between author/links and coment text */
.votelinks>center> :is(font+br, br+img) {
display: none;
}
.admin td {
padding: 0 1rem;
}
.admin td>p:before,
.admin td:before {
content: "βŽ” ";
font-size: 1.2em;
margin-right: 0.25em;
}
/* ---- FOOTER ---- */
#hnmain>tbody>tr:last-of-type:has(img) {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
transform: translateY(100%);
z-index: 99;
padding: 1rem 0;
}
/* footer: hide orange horizontal bar above footer */
img[src="s.gif"]+table {
display: none;
}
.yclinks {
display: flex;
align-items: center;
justify-content: center;
gap: var(--magic-num);
color: var(--text-color-alt);
margin: var(--magic-num) 0;
padding: var(--magic-num) 1rem;
width: max-content;
border-radius: calc(var(--border-radius) * 2);
}
.yclinks a:link {
color: var(--text-color-alt);
font-size: 0.9rem;
}
.yclinks a:hover {
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
text-decoration-color: var(--primary);
}
form[action="//hn.algolia.com/"] {
color: var(--text-color-alt);
font-size: var(--smaller);
display: flex;
align-items: center;
justify-content: center;
gap: 1ch;
}
/* yc application notice */
center>a[href$="ycombinator.com/apply/"] {
color: var(--text-color-alt);
text-decoration: underline;
text-underline-offset: 2px;
font-weight: 500;
}
}
@pmarreck
Copy link

Looks great over here, thanks @christippett !

@gingerbeardman
Copy link

gingerbeardman commented Jul 30, 2024

One request, comments that have had "-" clicked on them, marked as class .nosee are displayed very close to the following comment.

image

It would be great if the space could return underneath those. This seems to do it for me:

td.default > div:nth-child(1) {
  margin-bottom: 20px !important;
}

Screen shot 2024-07-30 at 23 25 26

@christippett
Copy link
Author

Good call. I'll make the change later today.

I also only just now made the connection between your account here and on HN... I commented on your macOS automation article completely oblivious that it was you. Thank you for not making it weird and picking my comment to take a screenshot of ;-)

@gingerbeardman
Copy link

OK, this is better:

td.nosee + td > div:first-child {
  margin-bottom: 10px !important;
}

@christippett
Copy link
Author

Sorry for the delay! Rather than piling on more styles, I removed the style I'd added that removed the original margin between comments.

@christippett
Copy link
Author

Unfortunately the diff is masked by whitespace changes (opps), what I changed was to remove .default br:has(+ div.comment) from the following section:

/* space between author/links and coment text */
.default br:has(+ div.comment),
.votelinks>center> :is(font+br, br+img) {
	display: none;
}

@gingerbeardman
Copy link

All good!

@pmarreck
Copy link

pmarreck commented Aug 22, 2024

I can't believe I'm saying this but I feel like CSS needs some kind of unit-testing framework lol

If only because a ton of the possible declarative changes you can make could result in regressions in other content that wasn't considered

It's possible that an LLM could help here because then you could assert on abstract notions like "make sure these voting controls in this one view are visible, but only when the related content is rolled over, and shaped like up/down triangles"

Just thinking aloud because I could probably build something like this if it was useful (and because I'm looking for work...)

@christippett
Copy link
Author

I've been giving this some thought too. What I think would be useful is to configure Playwright to take a bunch of screenshots of different pages which could then be manually reviewed before publishing a release. Each screenshot could have a side-by-side before/after view that would hopefully surface any obvious regressions or visual quirks...

@pmarreck
Copy link

That seems like a reasonable middle ground and would certainly automate most of the checking while also not forgetting anything!

@christippett
Copy link
Author

christippett commented Aug 23, 2024

The challenge is making sure that all possible states are accounted for. You'd probably want to test against static HTML to be as deterministic as possible, but the risk is you miss some minute change to the live site that messes everything up. Not forgetting that different users have varying levels of functionality available to them depending on their karma.

And then you start wondering if it's all a big over-engineered footgun and the easiest option is to just wait until someone leaves a comment here complaining about something not working πŸ˜‰

PS. @pmarreck I see you're into EDM. I'm about to make my way to go see LCD Soundsystem 🀘

@pmarreck
Copy link

pmarreck commented Aug 26, 2024

@christippett DUDE I AM SO JEALOUS. James Murphy is the MAN! Truly a creative inspiration. Ha, enjoy!!
Ever see this? https://www.youtube.com/watch?v=Zj9Sv1JpmPs
I just left this comment on it: "If 'edging' was a song" πŸ˜‚

@christippett
Copy link
Author

I'm going to play around with the upvote buttons again.. I miss the visual feedback of which posts/comments I've upvoted (the default hides the upvote arrows once voted). I've also unlocked downvote functionality for my account (501 karma), so I can easily see/test this particular use-case without relying on someone else's HTML dump!

@christippett
Copy link
Author

I've also started using this style on mobile which has surfaced a bunch of new issues (e.g. horizontal scroll at default zoom). I'd like to address these too as part of a future update.

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