Megaskin by Jockles
← Previous Chapter · Chapter Index · Next Chapter →
Chapter 5: iMessage CSS
Thanks to CodenameCarrot and La_Temperanza, whose How to Make iOS Text Messages on AO3 this module is built on. Copied and adapted from it: picture messages, the typing dots, and the hidden text for readers with the style off.
5.1 Chapter Contents
- 5.2 Getting the CSS
- 5.3 How the Module is Laid Out
- 5.4 The Window
- 5.5 Bubbles
- 5.6 Green Bubbles
- 5.7 Names, Status and System Lines
- 5.8 Edited Messages
- 5.9 Tapbacks
- 5.10 Pictures
- 5.11 Typing Dots
- 5.12 Message Bar
- 5.13 Links
- 5.14 Dark and Auto Themes
- 5.15 Taking Parts Out
This chapter goes through the CSS for the iMessage chats in the last chapter. It's the simplest of the modules, so it's a good one to learn from: every other module is built the same way, just with more parts.
5.2 Getting the CSS
The iMessage CSS is here: jockles.com/fic/megaskin/css/imessage.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.
5.3 How the Module is Laid Out
Every module is in the same order, so once you know your way around this one, you know your way around all of them:
- The main rules, which are the light theme: the window, the bubbles, and every other part.
- Links: the colour of links and
fake-links. - Dark theme: the colours that change for
theme-dark. - Auto theme: the same dark colours again, for
theme-autoon a device in dark mode.
Every rule in it has .imsg in it, so it can only ever touch iMessage chats.
5.4 The Window
#workskin .imsg {
background: #ffffff;
color: #000000;
border: 1px solid #d8d8dc;
border-radius: 14px;
}
#workskin .imsg .mob-head {
background: #f6f6f8;
color: #000000;
border-bottom: 1px solid #d8d8dc;
font-size: 0.85em;
}
#workskin .imsg .mob-head span {
display: block;
font-weight: normal;
color: #8e8e93;
font-size: 0.85em;
}
The core has already made the box phone-sized, so all this adds is the look: white, with a thin grey edge and rounded corners. The header is a slightly greyer white with a line under it. display: block on the span in the header is what puts "iMessage" on its own line under the name - a span normally sits on the same line as the text before it.
5.5 Bubbles
#workskin .imsg .msg {
display: table;
max-width: 75%;
margin: 0.18em 0;
padding: 0.45em 0.8em;
border-radius: 18px;
}
#workskin .imsg .out {
margin-left: auto;
background: #0a84ff;
color: #ffffff;
border-bottom-right-radius: 5px;
}
#workskin .imsg .in {
margin-right: auto;
background: #e9e9eb;
color: #000000;
border-bottom-left-radius: 5px;
}
display: tablemakes a paragraph only as wide as its words. A normal paragraph stretches across the whole chat, which would make every bubble the same width.max-width: 75%stops a long message going all the way across, so there's always a gap on the other side, like on a phone.margin-left: autopushes a sent bubble over to the right:autotakes up all of the spare room on that side. Received bubbles do the same on the right, which keeps them on the left.border-radius: 18pxrounds every corner, and the smaller5pxcorner at the bottom makes the little point on the side the bubble came from.
Changing the bubble colours
- Sent bubbles:
#0a84ffis iMessage blue. It's also used for your own tapbacks, and it appears in the dark and auto themes too, so change all of them - your text editor's find and replace is the easiest way. - Received bubbles:
#e9e9ebin light mode, and#262628in the dark and auto themes. - If you make a bubble lighter, change its
color(the text) to something dark, so it's still easy to read.
5.6 Green Bubbles
#workskin .imsg .out.sms { background: #30d158; color: #ffffff; }
.out.sms means a bubble that has both out and sms. Received messages are never green on a real iPhone, so the rule only works on sent ones - in sms does nothing.
5.7 Names, Status and System Lines
#workskin .imsg .who {
display: block;
font-size: 0.72em;
color: #8e8e93;
margin: 0.5em 0 0 0.9em;
}
#workskin .imsg .imsg-status {
display: block;
text-align: right;
font-size: 0.7em;
color: #8e8e93;
margin: 0.1em 0.4em 0.4em 0;
}
#workskin .imsg .mob-sys { color: #8e8e93; opacity: 1; }
#8e8e93 is Apple's grey, and it's used for all the small print. The who line is nudged in from the left (0.9em) so the name lines up with the text inside the bubble, not the edge of it. The core fades system lines with opacity, and iMessage puts it back to 1 because its grey is already faded enough.
5.8 Edited Messages
#workskin .imsg .imsg-edited, #workskin .imsg .imsg-edited-in {
display: block;
text-align: right;
font-size: 0.7em;
color: #0a84ff;
margin: 0.1em 0.4em 0.4em 0;
}
#workskin .imsg .imsg-edited-in { text-align: left; margin: 0.1em 0 0.4em 0.4em; }
It's the same small print as the status line, but in Apple's blue (#0a84ff), which shows up on both light and dark chats. imsg-edited-in is the left-hand version, under a message from the other person.
5.9 Tapbacks
Tapbacks are the fiddliest part, because they sit half outside the bubble. They work by taking the reaction out of the normal flow of the text and pinning it to the bubble's corner:
#workskin .imsg .msg.tapped {
position: relative;
margin-top: 1.7em;
}
#workskin .imsg .imsg-tap {
position: absolute;
top: -1.75em;
right: -0.95em;
min-width: 2.15em;
height: 2.15em;
padding: 0 0.35em;
box-sizing: border-box;
border: 2px solid #ffffff;
border-radius: 1.075em;
background: #e9e9eb;
color: #8e8e93;
font-size: 1em;
line-height: 1.9em;
text-align: center;
white-space: nowrap;
}
position: relativeon the bubble andposition: absoluteon the tapback go together. The tapback is placed using the bubble's edges, andtop: -1.75emandright: -0.95emmove it up and out past the top right corner.- Because it's been taken out of the flow, the tapback doesn't push anything out of the way, so
tappedadds space above the bubble (margin-top: 1.7em) for it to sit in. That's the only thingtappeddoes. - The white border is the same colour as the chat, so it looks like a little gap between the tapback and the bubble behind it.
border-radiusis half the height, which makes a circle, or a pill shape if the emoji is wide.
The two tail dots are made from nothing at all:
#workskin .imsg .imsg-tap::before,
#workskin .imsg .imsg-tap::after {
content: "";
position: absolute;
border-radius: 50%;
background: #e9e9eb;
}
#workskin .imsg .imsg-tap::before {
right: -0.2em;
bottom: -0.2em;
width: 0.5em;
height: 0.5em;
border: 2px solid #ffffff;
}
#workskin .imsg .imsg-tap::after {
right: -0.55em;
bottom: -0.5em;
width: 0.25em;
height: 0.25em;
}
::before and ::after add two extra boxes to every tapback, with content: "" meaning there's no text in them. Each one is rounded into a circle and placed off the bottom corner, a big one and then a small one, trailing away.
#workskin .imsg .out .imsg-tap { right: auto; left: -0.95em; }
#workskin .imsg .out .imsg-tap::before { right: auto; left: -0.2em; }
#workskin .imsg .out .imsg-tap::after { right: auto; left: -0.55em; }
#workskin .imsg .imsg-tap.me { background: #0a84ff; color: #ffffff; }
#workskin .imsg .imsg-tap.me::before,
#workskin .imsg .imsg-tap.me::after { background: #0a84ff; }
On your own messages, everything is flipped to the left: right: auto cancels the right-hand position, and left takes over. me turns the tapback and both its dots blue.
Stacked tapbacks in group chats
#workskin .imsg .imsg-tap { z-index: 4; }
#workskin .imsg .imsg-tap + .imsg-tap { z-index: 3; }
#workskin .imsg .imsg-tap + .imsg-tap + .imsg-tap { z-index: 2; }
#workskin .imsg .imsg-tap + .imsg-tap + .imsg-tap + .imsg-tap { z-index: 1; }
#workskin .imsg .imsg-tap + .imsg-tap { right: 0.5em; }
#workskin .imsg .imsg-tap + .imsg-tap + .imsg-tap { right: 1.95em; }
#workskin .imsg .imsg-tap + .imsg-tap + .imsg-tap + .imsg-tap { right: 3.4em; }
#workskin .imsg .imsg-tap + .imsg-tap::before,
#workskin .imsg .imsg-tap + .imsg-tap::after { display: none; }
+means "straight after". So.imsg-tap + .imsg-tapis the second tapback, and adding another+ .imsg-tapeach time gets the third and fourth.- Each one is moved a step further along the bubble (
0.5em,1.95em,3.4em), so they overlap like a fan. z-indexdecides which is in front. The first has the highest number, so it's on top.- Only the first keeps its tail dots.
- Sent messages have the same three lines with
leftinstead ofright. A fifth tapback would sit on top of the fourth - add another line with4.85emif you ever need one.
Keeping a sender's name next to their tapped message
#workskin .imsg .who + .msg.tapped { margin-top: 0.18em; }
#workskin .imsg .who:has(+ .msg.tapped) { margin-top: 1.5em; }
In a group chat, the space tapped adds would go between the name and the message, which looks wrong. The first line takes the space away from a tapped message that comes straight after a name. The second puts it above the name instead: :has(+ .msg.tapped) means "a name line that has a tapped message straight after it". If a reader's browser doesn't understand :has, the name just sits a bit closer to the message above - nothing breaks.
5.10 Pictures
#workskin .imsg .msg.pic { width: 55%; padding: 0.2em; overflow: hidden; }
#workskin .imsg .msg.pic img {
display: block;
width: 100%;
height: auto;
border-radius: 15px;
}
#workskin .imsg .msg.pic.text { padding: 2.5em 1em; font-style: italic; text-align: center; }
A picture bubble is a fixed 55% of the chat wide, whatever size the picture is, and the picture fills it. height: auto keeps the picture's shape, so nothing gets squashed. With text, the description gets lots of padding so it's about the size of a photo.
5.11 Typing Dots
#workskin .imsg .typing-indicator {
display: table;
margin: 0.3em 0;
padding: 0.6em 0.8em;
border-radius: 18px;
border-bottom-left-radius: 5px;
background: #e9e9eb;
}
#workskin .imsg .typing-indicator span {
display: inline-block;
width: 0.6em;
height: 0.6em;
margin-right: 0.25em;
border-radius: 50%;
background: #8e8e93;
opacity: 0.5;
}
#workskin .imsg .typing-indicator span:nth-child(2) { opacity: 0.7; }
#workskin .imsg .typing-indicator span:nth-child(3) { opacity: 1; margin-right: 0; }
The bubble is built like a received message. Each empty span becomes a small grey circle, and :nth-child(2) and :nth-child(3) - the second and third dot - get darker, so it looks like the dots are mid-bounce.
5.12 Message Bar
#workskin .imsg .imsg-input {
display: flex;
align-items: center;
margin: 0;
padding: 0.45em 0.7em 0.65em 0.7em;
}
#workskin .imsg .imsg-plus {
flex: none;
width: 1.55em;
height: 1.55em;
margin-right: 0.35em;
border-radius: 50%;
box-sizing: border-box;
border: 1px solid #8e8e93;
background: #ffffff;
color: #000000;
font-size: 1.3em;
font-weight: 300;
line-height: 1.45em;
text-align: center;
}
#workskin .imsg .imsg-field {
flex: 1;
position: relative;
padding: 0.45em 2.4em 0.45em 0.9em;
border: 1px solid #8e8e93;
border-radius: 1.3em;
background: #f0f0f2;
color: #6c6c70;
}
#workskin .imsg .imsg-mic {
position: absolute;
right: 0.7em;
top: 50%;
transform: translateY(-50%);
font-size: 0.95em;
}
display: flexlines the + button and the box up in a row, andalign-items: centercentres them against each other.flex: nonekeeps the + button the same size, andflex: 1lets the box take up all the room that's left.- The microphone is pinned inside the right end of the box, the same way as a tapback.
top: 50%withtranslateY(-50%)is the usual way to centre something from top to bottom: move its top edge halfway down, then move it back up by half its own height. - The extra padding on the right of the box (
2.4em) leaves room for the microphone, so the word never runs under it.
For a half-typed message, typed turns the words black (white in dark mode), and the send arrow takes the microphone's place:
#workskin .imsg .imsg-field.typed { color: #000000; }
#workskin .imsg .imsg-send {
position: absolute;
right: 0.3em;
bottom: 0.3em;
width: 1.7em;
height: 1.7em;
line-height: 1.7em;
border-radius: 50%;
background: #0a84ff;
color: #ffffff;
font-weight: bold;
text-align: center;
}
The arrow is pinned to the bottom corner rather than the middle, so on a long message it sits by the last line, like the real one.
5.13 Links
#workskin .imsg .in .fake-link, #workskin .imsg .in a:not(.tl) { color: #0a84ff; }
#workskin .imsg .out .fake-link, #workskin .imsg .out a:not(.tl) { color: #ffffff; }
Links are blue in grey bubbles and white in blue ones, so they show up on both.
5.14 Dark and Auto Themes
The dark theme is the light rules again, with only the colours that change:
#workskin .imsg.theme-dark { background: #000000; color: #ffffff; border-color: #2c2c2e; }
#workskin .imsg.theme-dark .mob-head { background: #1c1c1e; color: #ffffff; border-bottom-color: #2c2c2e; }
#workskin .imsg.theme-dark .in,
#workskin .imsg.theme-dark .typing-indicator {
background: #262628;
color: #ffffff;
}
...and so on for the tapbacks and the message bar. Sent bubbles don't appear, because iMessage blue is the same in both.
The auto theme is exactly the same block, with theme-auto in place of theme-dark, inside this:
@media (prefers-color-scheme: dark) {
#workskin .imsg.theme-auto { background: #000000; color: #ffffff; border-color: #2c2c2e; }
...
}
Everything inside @media (prefers-color-scheme: dark) { ... } only counts when the reader's device is in dark mode. Otherwise theme-auto doesn't match anything, and the chat uses the light rules.
Changing a dark colour
Because AO3 doesn't allow variables, a dark colour is written in both the dark block and the auto block. Change it in both, or theme-dark and theme-auto chats won't match. Find and replace in a text editor catches every copy at once.
5.15 Taking Parts Out
If you never use a part, you can delete its rules to make your skin shorter. Each part has a comment above it saying what it is. The ones that come out cleanly are:
- Tapbacks: everything with
tappedorimsg-tapin it, including the lines in the dark and auto blocks. - Green bubbles: the
.out.smsline. - Pictures: the three
.msg.picrules. - Typing dots: the four
typing-indicatorrules, and.typing-indicatorin the dark and auto blocks. - Message bar: everything with
imsg-input,imsg-plus,imsg-fieldorimsg-micin it.
Leave the window, the bubbles and the theme blocks, which everything else depends on.