Actions

Work Header

Rating:
Archive Warning:
Fandom:
Additional Tags:
Language:
English
Stats:
Published:
2026-08-16
Words:
811
Chapters:
1/1
Comments:
6
Kudos:
14
Bookmarks:
8
Hits:
119

Demo for Accessible Text Messaging Chat Bubbles

Summary:

An iOS inspired text thread theme that plays nice with screen readers, varying page widths, scaling fonts, and light & dark themes.

Notes:

I've done my best to make this code as accessibility-friendly as possible, but if you encounter a problem or have a thought about how to make it better, I would love to hear it! There are a number of features I would have liked to include that just aren't possible with the current AO3 CSS support, and as functionality is expanded I hope to update and improve this code. :)

Work Text:

Ephyna: I need some funny sample text for this demo.

Ephyna: Tell me a joke

Bestie: lol no pressure

Bestie: why did the cowboy get a wiener dog?

Ephyna: Why?

Bestie: because everyone told him to get a long little doggy!

Ephyna: lol XD

Ephyna: Ok so I just looked this up, and for both doggos and hot-doggos the term 'wiener' comes from 'Wienerwurst,' which literally translates to 'Vienna sausage' in German.

Bestie: vienna would be a cute name for a puppy!


This is some text messaging chat code I wrote for my good friend NobodyInParticular123456!

Some features included in how it's set up:

  • The width flexes with the browser when it gets narrow, so it's mobile friendly!
  • The whole thing will smoothly scale in size to match the reader's font settings.
  • It has hidden text to label who is talking so it will be screen-reader friendly for our blind and low-vision readers, or if someone has workskins disabled. 💜
  • The colors were chosen to look nice and stay readable on both light and dark backgrounds. Don't forget that not everyone uses the same theme as you, and forcing a theme upon someone can cause them eye strain. (Ideally the light bubbles would change to dark with a dark theme, but current AO3 css blocks the code needed for this.)




The Anatomy of the HTML

Wrap your whole chat block in a div with the 'chat' class:

<div class="chat">
...
</div>

Each individual chat bubble has 3 components:

  1. The main wrapper div with the 'bubble' class and either a 'local' or 'remote' class depending on which of your characters is talking.
    <div class="bubble local"> ... </div>
    <div class="bubble remote"> ... </div>
  2. The message of the chat bubble which is a p tag with the 'message' class on it.
    <p class="message"> ... </p>
  3. And the hidden accessibility text to indicate who is talking. This is a 'span' element with the 'ally' class, which goes inside the 'message' element, and before the actual message text.
    ❗Important: Include a space between this 'span' element and your message text to ensure the character's name and the first word of the message are not read out as a single word by screen readers. It also makes your code a little more readable. :)
    <span class="a11y">Your_Character_Name:</span> 

Stack multiple messages within the outer 'chat' div, and you should end up with something like this:

<div class="chat">
  <div class="bubble local"><p class="message"><span class="a11y">Ephyna:</span> Tell me a joke</p></div>
  <div class="bubble remote"><p class="message"><span class="a11y">Bestie:</span> lol no pressure</p></div>
</div>

✨Tip: Double check that all of the quote marks in your code are straight ones ' ' and not curly ones ‘ ’. Curly ones won't work!




The CSS for Your Workskin:

These CSS styles will work out of the box as long as you follow the HTML structure outlined above. They must be placed in a workskin; you can't paste them directly into your fic's chapter. Check out the AO3 FAQ for how to set up workskins if haven't made one before.

If you want to adjust the colors, the blue is set in .chat .local .message and .chat .local::after, and the grey in .chat .remote .message and .chat .remote::after. There are two hex codes for each that are repeated several times for the gradients. Make sure you don't forget the ::after colors — those are for the curly tail on the bubbles! :)

The fonts are set in the selectors .chat for remote and .chat .local for local messages.

If you do change the colors, make sure you test your work on both light and dark backgrounds!

.chat {
  position: relative;
  z-index: 1;
  color: #111;
  padding: 0 0.6rem;
  max-width: 32rem;
  overflow: hidden;
}

.chat .bubble {
  position: relative;
  display: block;
  margin: .5rem 0;
  max-width: 80%;
}
.chat .remote+.local,
.chat .local+.remote {
  margin-top: .8em;
}

.chat .message {
  border-radius: 1rem;
  padding: .5rem 1rem;
  margin: 0;
  font-size: 0.85rem;
  display: inline-block;
}
.chat .bubble::after {
  content: "";
  position: absolute;
  z-index: -1;
  display: block;
  background-color: transparent;
  width: 1.75rem;
  height: 1.5rem;
  bottom: -0.3rem;
}
.chat .local {
  text-align: right;
  color: #fff;
  margin-left: auto;
}
.chat .local .message {
  text-shadow: #1E36D1 0 0 0.75rem;
  background: linear-gradient(-15deg, #1E36D1, #0C66F7);
  box-shadow: inset -0.25rem -0.25rem 2rem -0.5rem #1E36D1,
  inset 2.5rem 2rem 2.5rem -2.75rem #0C66F7;
}

.chat .local::after {
  right: -1.75rem;
  border-bottom-left-radius: 66%;
  box-shadow: -1rem -0.1rem 0 -0.2rem #1E36D1;
}
.chat .remote {
  text-align: left;
}
.chat .remote .message {
  text-shadow: #D2D4D4 0 0 0.75rem;
  background: linear-gradient(15deg, #D2D4D4, #F7F8F8);
  box-shadow: inset 0.25rem -0.25rem 2rem -0.5rem #D2D4D4,
  inset -2.5rem 2rem 2.5rem -2.75rem #F7F8F8;
}
.chat .remote::after {
  left: -1.75rem;
  border-bottom-right-radius: 66%;
  box-shadow: 1rem -0.1rem 0 -0.2rem #D2D4D4;
}

.chat .a11y {
  position: absolute;
  clip: rect(1px, 1px, 1px, 1px);
  overflow: hidden;
  height: 1px;
  width: 1px;
  padding: 0;
  border: 0;
}