jockles.com

All the Fannish things I do.

Megaskin by Jockles

← Previous Chapter · Chapter Index · Next Chapter →

Chapter 17: Discord CSS

Thanks to BookKeep, whose Discord Work Skin & Tutorial this module is mostly adapted from (BookKeep asks to be credited in your author's notes if you use it), and to TrojanTeapot for 2023 Discord Theme Workskin. Copied and adapted from them: the window and channel header, message layout, replies, reactions, spoilers, mentions, embeds, bots, and code blocks.

17.1 Chapter Contents

This chapter goes through the CSS for the Discord chats in the last chapter: the window, the channel header, messages and their pictures, replies, reactions, spoilers, dividers, and the dark theme.

17.2 Getting the CSS

The Discord CSS is here: jockles.com/fic/megaskin/css/discord.css. It needs the core CSS too (jockles.com/fic/megaskin/css/core.css), so paste the core into your work skin first and this underneath it.

17.3 How the Module is Laid Out

The usual order - the main rules, Links, Scrolling, Dark theme and Auto theme - but Discord is the odd one out in a few ways:

  • It doesn't use the mob box. Discord is mostly used on a computer, so it's a wide window that fills the page, unless you add narrow.
  • It does its own scrolling, and has its own no-scroll rule.
  • The little icons in the header and the message box are pictures hosted on Imgur, not text.
  • It uses rem for most sizes, where the other modules use em. A rem is the size of the page's normal text, so the sizes stay the same wherever the chat sits.

17.4 The Window

#workskin .dc {
  margin: 1.2em 0;
  position: relative;
  padding: 0 0 1rem 0;
  font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 1rem;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  overflow: hidden;
  vertical-align: baseline;
  border: 1px solid rgb(54, 57, 63);
  border-radius: 0.25rem;
  text-align: left;
  color: rgb(46, 51, 56);
  background-color: rgb(255, 255, 255);
  border-color: rgb(227, 229, 232);
}

#workskin .dc.narrow {
  width: 380px;
  max-width: 100%;
  margin: 1.2em auto;
}
  • A white window with a pale grey edge and only slightly rounded corners. Whitney was Discord's font for years - readers who don't have it get Helvetica or Arial instead.
  • position: relative matters more than it looks: the profile circles are placed with position: absolute, and this makes them measure from the edge of the window.
  • narrow makes it the same width as the phone chats and centres it, for when you want Discord on a phone.
#workskin .dc p,
#workskin .dc a:not(.tl) {
  margin: 0;
  padding: 0;
  border: 0;
}

This takes away AO3's gaps between paragraphs, so the message lines sit tight together like they do in Discord.

17.5 The Channel Header

#workskin .dc .dc-header::before {
  content: '';
  background-repeat: no-repeat;
  background-size: contain;
  position: absolute;
  left: 1rem;
  top: 1rem;
  height: 1.5rem;
  width: 1.5rem;
}

#workskin .dc .hashtag::before { background-image: url(https://i.imgur.com/XbvXZWD.png); }

#workskin .dc .locked::before { background-image: url(https://i.imgur.com/MwjtrTT.png); }

#workskin .dc .at-symbol::before { background-image: url(https://i.imgur.com/0w9EkYY.png); }

The symbol before the channel name is an empty box made with ::before, with a picture as its background. Adding hashtag, locked or at-symbol to the header picks which picture: # for a channel, a padlock for a private channel, or @ for a DM. The dark theme swaps each one for a lighter version, so they show up on the dark background.

#workskin .dc .dc-header::after {
  content: "";
  position: relative;
  display: block;
  top: 18px;
  left: 0;
  right: 0;
  height: 1px;
  box-shadow:
    rgba(4, 4, 5, 0.2) 0px 1px 0px 0px,
    rgba(6, 6, 7, 0.05) 0px 1.5px 0px 0px,
    rgba(4, 4, 5, 0.05) 0px 2px 0px 0px;
  z-index: 1;
}

#workskin .dc .dc-header > h3 {
  padding-left: 3rem !important;
  padding-right: 3rem !important;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
  • The line under the header is ::after - three thin shadows stacked up, which fade out like Discord's soft edge instead of a hard line.
  • The channel name is pushed in to clear the symbol, kept on one line, and cut off with "..." if it's too long. !important stops AO3's own heading padding from winning.
#workskin .dc .dc-header-divider {
  border-left: 1px solid;
  display: inline;
  margin: 0 0.5rem 0 1rem;
  height: 24px;
  border-color: rgb(46, 51, 56);
}

The little upright line between the channel name and its description is an empty span with only a left border.

17.6 The Message Box

#workskin .dc .dc-input {
  margin: 1.5rem 1rem 0;
  padding: 1rem 3.5rem;
  border-radius: 0.5rem;
  position: relative;
  min-height: 1.5rem;
  background-color: rgb(235, 237, 239);
}

#workskin .dc .dc-input-text::before {
  content: '';
  background-repeat: no-repeat;
  background-size: contain;
  position: absolute;
  left: 1rem;
  height: 1.5rem;
  width: 1.5rem;
  background-image: url(https://i.imgur.com/JB3HgBA.png);
}

The grey "Message #general" box at the bottom, with a round button on its left - another Imgur picture in a ::before.

17.7 Messages and Names

#workskin .dc .dc-message { min-height: 2.75rem; margin-top: 1.0625rem; padding: 0.125rem 3rem 0.125rem 4.5rem; }

#workskin .dc .dc-name::before {
  content: '';
  background-color: rgb(88, 101, 242);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  position: absolute;
  border-radius: 50%;
  margin-top: 0.125rem;
  left: 1.25rem;
  height: 2.5rem;
  width: 2.5rem;
}
  • Every message is pushed in 4.5rem from the left, which leaves room for the profile circle.
  • The circle belongs to the name, not the message: it's the name's ::before, taken out of the line with position: absolute and put 1.25rem from the window's left edge. That's why a message with no name (a second message from the same person) has no circle, just like Discord.
  • min-height makes each message at least as tall as the circle, so short messages don't overlap the next one.

Adding colour1 to colour10 to a name does two jobs: it colours the name, and (because it's on the name) the circle too. The colours are in the core, shared with every app, with brighter shades for names and lighter ones again in the dark theme - see The Ten Colours in the core CSS chapter. With no colour class, the circle is Discord blue.

#workskin .dc .dc-pfp {
  display: inline-block; /* inline, like the plain circle, so Firefox keeps it level with the name */
  position: absolute;
  left: 1.25rem;
  margin-top: 0.125rem;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  overflow: hidden;
}

#workskin .dc .dc-name:has(.dc-pfp)::before { display: none; }

For a picture or a profile from the core, a dc-pfp span goes inside the name, sitting exactly where the circle would be. The second rule says "if a name has a dc-pfp in it, don't draw the plain circle" - otherwise you'd get both.

More about :has()

:has() picks something by what's inside it. If a reader's browser doesn't understand it, the rule is skipped, and the coloured circle sits behind the picture, so it still looks fine.

#workskin .dc .dc-time,
#workskin .dc .dc-edited {
  margin-left: 0.25rem;
  font-size: 0.75rem;
  line-height: 1.375rem;
  white-space: nowrap;
}

#workskin .dc .dc-text {
  font-size: 1rem;
  line-height: 1.375rem;
  white-space: break-spaces;
  word-wrap: break-word;
  padding: 0.125rem 0 0.125rem 0;
}

The time and "(edited)" are small and grey and never split across lines. break-spaces keeps any extra spaces you type in a message, and break-word breaks up very long words (like a keysmash) so they don't run off the edge.

17.8 Replies

#workskin .dc .dc-reply::before {
  content: '';
  display: block;
  position: absolute;
  box-sizing: border-box;
  top: 0.625rem;
  right: 100%;
  bottom: 0;
  left: -2.25rem;
  margin: -0.0625rem 0.25rem -0.125rem -0.0625rem;
  border-style: solid;
  border-width: 0.125rem 0 0 0.125rem;
  border-top-left-radius: 0.375rem;
  border-color: rgb(199, 204, 209);
}

The curved line from a reply up to the message it's answering is a box with only a top and a left border, and its top-left corner rounded. That's the whole trick - the rest of the box is invisible.

#workskin .dc .dc-reply .dc-text {
  position: relative;
  left: 1rem;
  font-size: 0.875rem;
  height: 1.125rem;
  padding: 0;
  line-height: 1.125rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: inline-block;
  max-width: 80%;
  transform: translate(0%, 25%);
}

The quoted message is small, on one line, and cut off with "..." if it's long. The name in a reply gets a smaller circle (1rem), set up the same way as the big one.

17.9 Reactions and Mentions

#workskin .dc .dc-reaction.me {
  background-color: rgb(231, 233, 253);
  border-color: rgb(88, 101, 242);
  color: rgb(71, 82, 196);
}

#workskin .dc .dc-message:has(.dc-mention.me) {
  background-color: rgba(250, 168, 26, 0.1);
  border-left: 2px solid rgb(170, 114, 18);
}
  • Reactions are grey pills, but adding me to one makes it blue with a blue edge, like a reaction your POV character has added.
  • Adding me to a mention lights up the whole message in yellow, with a line down its left side - what Discord does when someone @s you. This is :has() again: "a message that has a dc-mention me inside it".

17.10 Spoilers

#workskin .dc .dc-spoiler-text {
  border-radius: 0.1875rem;
  display: inline-block;
  cursor: pointer;
  color: rgb(185, 187, 190);
  background-color: rgb(185, 187, 190);
  transition: all 0s 2147483647s;
}

#workskin .dc .dc-spoiler-text:active {
  color: rgb(46, 51, 56);
  background-color: rgb(229, 229, 229);
  transition: all 0s;
}

This is one of the cleverest tricks in the megaskin, and it's BookKeep's. The spoiler text starts the same colour as its background, so you can't read it. :active means "while it's being clicked", and then the text turns dark and readable.

On its own, that would hide the text again as soon as you let go. But the first rule has transition: all 0s 2147483647s - "wait 2,147,483,647 seconds before changing back". That's about 68 years, so once it's revealed it stays revealed.

The :active rule has transition: all 0s, so the reveal happens instantly. That matters: if the reveal took even a tenth of a second, a quick click would let go halfway through, and the 68-year wait would freeze the spoiler half-revealed, so the reader would have to click again and again.

#workskin .dc .dc-spoiler > img {
  opacity: 0;
  position: relative;
  vertical-align: bottom;
  z-index: 10;
  background-color: rgb(255, 255, 255);
  transition: all 0s 2147483647s;
}

#workskin .dc .dc-spoiler:before {
  content: 'SPOILER';
  ...
}

#workskin .dc .dc-spoiler:active > img { opacity: 1; z-index: 10; transition: all 0s; }

Spoiler pictures work the same way: the picture starts see-through (opacity: 0) over a grey box with "SPOILER" on it, and clicking it shows the picture for good. (I've cut the middle of the SPOILER label rule here - it's just its size, colours, and centring.)

17.11 Dividers

#workskin .dc .dc-divider-label {
  padding: 0.125rem 0.25rem;
  line-height: 0.8125rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: 0.5rem;
  display: block;
  position: absolute;
  right: 50%;
  top: 50%;
  transform: translate(50%, -50%);
  z-index: 5;
  background-color: rgb(255, 255, 255);
}

The date dividers are a thin line with a label sitting on it. The label is moved to the middle (right: 50%; top: 50%), then transform nudges it back by half its own size so it's perfectly centred. Its background is the same colour as the window, which hides the line behind the words.

#workskin .dc .dc-new-tag::before {
  content: '';
  left: -0.3rem;
  position: absolute;
  width: 0;
  height: 0;
  border-top: 0.375rem solid;
  border-bottom: 0.375rem solid;
  border-right: 0.5rem solid;
  border-right-color: rgb(216, 60, 62);
  border-top-color: rgb(255, 255, 255);
  border-bottom-color: rgb(255, 255, 255);
}

The red "NEW" tag has a pointed end, made with a CSS triangle: a box with no size and three thick borders. The right border is red and the top and bottom ones are the window colour, so all you see is a red arrow point.

17.12 Bots and Typing

#workskin .dc .dc-bot::before {
  content: '\00a0\00a0\00a0\00a0 BOT';
  color: rgb(255, 255, 255);
  background-color: rgb(88, 101, 242);
  background-image: url(https://i.imgur.com/3Ox94zk.png);
  ...
}

The BOT badge is written in by the CSS. \00a0 is a space that can't be squashed, and the four of them leave room for the tick picture before the word. (Again, I've cut the sizing from the middle.)

#workskin .dc .typing-indicator span:nth-child(1) { opacity: 0.4; transform: scale(0.6, 0.6); }

#workskin .dc .typing-indicator span:nth-child(2) { opacity: 0.6; transform: scale(0.8, 0.8); }

#workskin .dc .typing-indicator span:nth-child(3) { opacity: 0.9; transform: scale(1, 1); }

The three typing dots get bigger and darker from left to right, which gives a frozen version of Discord's bouncing dots.

17.13 Links and Scrolling

#workskin .dc a:not(.tl):hover { color: rgb(0, 104, 224); text-decoration: underline; }

#workskin .dc { max-height: 640px; overflow-y: auto; overflow-x: hidden; }

#workskin .dc.no-scroll {
  max-height: none;
  overflow-y: visible;
}

Links are Discord's blue, and get underlined when you hover. The whole window scrolls once it's taller than 640px - the header and the message box scroll with it. Add no-scroll to the dc box to switch it off.

17.14 Themes

The dark theme is Discord's classic dark grey (rgb(54, 57, 63)) with light text. Code blocks, embeds and reactions go a shade darker, links go brighter, the names get their lighter colours, and the header icons and the message box button swap to their light Imgur versions. The spoilers get their own dark colours, with the same 68-year trick. The auto theme is the same again inside @media (prefers-color-scheme: dark), and plain dc or theme-light is the light theme.

17.15 Taking Parts Out

Search for the class names and delete every rule with them in, including in the dark and auto blocks:

  • Header: everything starting dc-header, plus dc-description, hashtag, locked and at-symbol.
  • Message box: dc-input and dc-input-text.
  • Replies: dc-reply.
  • Pictures and emoji: dc-image, dc-emoji and dc-inline-emoji.
  • Reactions: dc-reaction and dc-reaction-count.
  • Embeds: everything starting dc-embed.
  • Quotes and code: dc-quote, dc-quote-line, dc-quote-text and dc-code.
  • Spoilers: dc-spoiler and dc-spoiler-text.
  • Mentions: dc-mention and dc-role-mention.
  • Dividers: dc-divider, dc-divider-label, dc-new-divider and dc-new-tag.
  • Extras: dc-new-user, dc-bot, and the typing dots (dc-typing, typing-indicator and dc-typing-text).

Keep the window, dc-message, dc-name, dc-time and dc-text, which every message uses.

← Previous Chapter · Chapter Index · Next Chapter →