F00FC7C8's Guide to CSS on Neospring

You were probably linked this guide because you asked how to customize your Neospring profile using CSS, and didn't provide any specifics, or because you were looking for a certain commonly circulated piece of CSS code. Here I try to provide the minimum of CSS knowledge needed to customize your page effectively, in a way that someone with limited prior knowledge can understand. It may be possible to extend this guide to other sites that allow customization via CSS, but I would not recommend using this guide to learn CSS for building personal websites; for that I recommend using the guides on W3Schools.

If you're just looking for code to copy to do something you saw on someone else's profile, click here for some codes.

In case this guide is confusing to you, or you think something should be added or rephrased, please drop a suggestion or clarifying question in my Neospring inbox. You can also contact me for help following this guide or help with CSS in general.

Table of contents

A short crash course on web tech

A typical webpage consists of code in three different languages: HTML (HyperText Markup Language), CSS (Cascading Style Sheets), and JS (JavaScript). It's necessary to understand how these three languages interact in order to code effectively in CSS.

HTML

HTML is a markup language that defines the layout of a page, and it looks like this:

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1 id="hello">Hello World!</h1>
<p>This is an illustrative example of what an HTML page looks like.</p>
<p class="red">Don't be intimidated! It's not as complex as it looks!</p>
</body>
</html>

You don't need to be able to write HTML for this tutorial, but you do need to understand its basic structure: HTML is composed of a hierarchy of elements. Each element has an opening tag (<element_name>), and most have a closing tag (</element_name>). Everything between the opening and closing tags is considered part of the element; consequently, elements can contain other elements. Elements can also have attributes specified in the opening tag, like this: <element attribute="value">.

JS

JavaScript is a programming language, used to implement a page's functionality, and it looks like this:

1
2
3
4
5
const hour = new Date().getHours();

if (hour == 10) {
  document.getElementById("time").innerHTML = "Happy 10:00!"; 
}

It can manipulate HTML elements and CSS styles in various ways, but the ways Neospring uses it will most likely not be relevant to you. Just know that you can't edit Neospring's HTML or JavaScript, so you can't fundamentally change the functionality or layout of the page. For example, you can't put an extra button into the navbar without HTML, and you can't add an interactive Tetris game to your page without JS. If you find a website that allows you to edit JavaScript, aside from services like Neocities that allow you to make entire custom websites, you should probably avoid it, because it is very easy to use JS to track users, steal login info, install malware, etc.

CSS

CSS is a stylesheet language that defines the visual theme of a page, and it looks like this:

body {
  font-family: sans-serif;
}

#hello {
  font-weight: 700;
  background-color: #ff0;
}

.red {
  color: red;
}

CSS code consists of selectors, which define a set of HTML elements, followed by a list of properties inside curly brackets which define how that element looks. Selectors are quite versatile, and allow you to select elements in a few different ways:

  • Element names, such as body or p
  • An element's class attribute preceded by a period, such as .red or .card-nest
  • An element's id attribute preceded by a pound sign, such as #hello or #profile_box
  • A type of element paired with any of its attributes, using the syntax element[attribute="value"], for example a[href="/"]
  • A list of other selectors separated by commas, such as p, .red, #hello, a[href="/"]
  • A selector for an element, preceded by a selector for an element that contains it, such as div p

This list is not exhaustive; CSS has enough features to build a selector for almost any combination of elements you wish.

CSS will apply properties to the most broad selectors first, and the most narrow selectors last. If you set a font on body, then that font will also apply to everything inside that container, but if you set a different font for #question_box, it will supersede the one you set for #profile_container. You can prevent more specific selectors from overriding more general ones by adding !important before the semicolon in a CSS property. It's best to avoid using !important if you can, but it may sometimes be necessary in a context like this, where you're limited to adding to existing CSS.

We'll examine properties more closely later, as each has its own syntax, but in general, they look like this: property_name: value;. Don't forget the semicolon after each property; it is needed to separate properties from each other.

Another common mistake in CSS is to forget the opening curly bracket { after a selector, or the closing bracket } after a list of properties. Missing brackets can cause large sections of your code to be ignored. A mistake like this is easy to make if your CSS is not cleanly formatted; if that's the case, it's a good idea to organize your code using a "beautifier" tool such as this one.

Commonly used properties

The possibilities of CSS for visual customization are virtually limitless, but some things come up more often than others. Here are the ones you're most likely to need:

  • color: The text color. There are numerous formats allowed for it:
    • A common name such as blue or red
    • A hex code, either in short form (#ff0 = yellow) or long form (#ffff00), optionally with opacity (#ff08 == #ffff0088 == transparent yellow.)
    • RGBA format (rgba(red 0-255, green 0-255, blue 0-255, alpha/opacity 0-255) - for example rgba(0, 0, 0, 127) is transparent black, also supports percentages like 0-100%)
    • HSL format (hsl(hue 0-100%, saturation 0-100%, lightness 0-100%) - for example hsl(0%, 100%, 75%) is pink)
  • background: The element's background. All the formats for color are supported, in addition to a few others:
    • linear-gradient(colors, separated, by, commas): A horizontal gradient between multiple colors. For example, linear-gradient(red, blue) is a vertical gradient from red to blue.
    • url(image_url): Use the image at "image_url" as the background. Note that due to Neospring's caching, some sites may not work; see file hosts known to work with Neospring below.
  • background-size: How the element's background is processed. If your image is repeating or being cropped and you don't want it to, use cover.
  • border: The border around the element. Though several formats are supported, you're most likely to use this one:
    • [size]px solid [color]: A solid color border. "size" is a number of pixels defining the width of the border, usually just 1 pixel. "color" is any of the formats supported by the color property above.
  • display: Selects one of various frameworks for organizing elements. Most commonly, you'll just use this to make elements disappear: display: none;.

Nearly all properties will also accept inherit as an option, which means to use the settings of the containing element rather than whatever the default for that element is. This can be used to revert some of Neospring's settings.

Fonts

Fonts are perhaps the most common use of CSS on Neospring, and they involve a few different features of CSS, so I've given them their own section.

To set a font for a particular element or all elements, you must use the font-family property:

1
2
3
body {
  font-family: myFont;
}

But this code, on its own, doesn't tell the web browser what "myFont" is. Without any more information, the browser will search your operating system's font directory for a font named "myFont", and if it does not find it, it will fall back to a default font, which will vary between browsers. If you'd like your page to appear similar on all browsers, but are okay with it not appearing identical, you can use one of five "generic" fonts, which will almost always refer to a font installed on your system, depending on the system settings:

  • serif refers to fonts with small strokes along the edges, such as Times New Roman, Computer Modern, or the fonts typically used for formal writing.
  • sans-serif refers to fonts with clean lines, such as the one you're probably reading this article in right now.
  • monospace refers to typewriter-like fonts, and will usually use the same font as text editors and terminals.
  • cursive refers to fonts that imitate handwriting.
  • fantasy refers to decorative fonts.

You can also use one of these generic fonts (or any font) as a backup in case your preferred one fails to load:

1
2
3
body {
  font-family: myFont, sans-serif;
}

But most likely, you have a specific font in mind. In that case, you need a @font-face query, which is a special statement in CSS that defines a font using a name, URL, and optionally other features of the font:

1
2
3
4
5
6
7
8
@font-face {
  font-family: myFont;
  src: url('https://example.com/myFont.ttf');
}

body {
  font-family: myFont;
}

The above code is sufficient to get "myFont" working. The web browser will see font-family: myFont; under body, and know what to do because there is a @font-face query which also has font-family: myFont;. Keep in mind that the URL you provide must be a direct link to a font file in TTF, OTF, or WOFF format. Linking a ZIP file that contains font files will not work.

Sometimes an online download for a font will contain multiple TTF files for different weights and styles of a font. For the sake of simplicity, it is best to use only the "Regular" version. You can include all of the variants individually, using different @font-face properties to distinguish them, and they might make bold or italic text look better, but I've never seen anyone on Neospring who does this, or has expressed a desire to. If you'd like me to expand on how to do that anyway, let me know.

Regardless of your font, you can configure the appearance of text in any element with several other properties:

  • font-size: The size of text. Can be set in pixels (<number>px), points (<number>pt), or multiples of the default size (<number>em). Usually 11pt or 11px or leaving it unchanged is a good choice, but depending on which custom font you're using, a lower or higher number might be more readable.
  • font-weight: The "boldness" of text, defined as a number between 100 and 1000. 400 is "regular", 700 is "bold", and 100 is "light". I don't recommend messing with this if you have a custom font.
  • font-style: Can be normal, italic, or oblique. I also don't recommend messing with this if you have a custom font.

Borders

Borders are visual features which appear along the edges of an element. Normal borders use the CSS border property, with the syntax border: <width> <style> <color>;. Width can be in pixels (px), points (pt), ems / font size units (em), or any other value supported by properties like font-size. Color can be in any format supported by the color property. style can be any of several variants:

  • solid: A typical solid-color border. For example, a 1-pixel-wide white outline would be border: 1px solid white;.
  • dashed: A dashed border. For example, content warnings on Neospring use border: dashed 2px var(--color-super-lowered);.
  • double: Two concentric borders. Example: border: 1rem double blue;
  • ridge: A bevel consisting of the chosen color and a darker version of the same color. Example: border: 1em ridge #aaa;

You can have different borders on different sides of an element by using the properties border-top, border-bottom, border-left, and border-right with the same syntax.

Border images require a solid border (border: 15px solid; is sufficient), in addition to the border-image property, which is more complicated. The syntax you will usually use for this property is <source> <slice> <repeat>. Each of these fields needs to be explained:

  1. The source can be either a URL (with the syntax url(<link here>)), or a gradient (like linear-gradient(red, blue)).
  2. The slice is the portion of the border image's width/height that is taken up by the sides of the border. This depends on the image and is usually somewhere around 30%, but getting the right value will require some trial and error. If the borders are extremely blurry, it's probably too low, and if the pattern doesn't fill the sides and/or tile properly, it's probably too high. Adding the keyword fill after the slice value will cause the middle of the border image to be used as a background for the entire element.
  3. The repeat is the way the sides of the border fill the space between the corners;. Typically you want round, which simply repeats the pattern along the sides and stretches it slightly to fill the remainder, but for some border images space (which puts space between border image tiles instead of stretching them) or repeat (which clips border image tiles instead of repeating them) may look better.

Putting it all together, a good border image code will look something like this:

1
2
3
4
.card-nest {
  border: 15px solid;
  border-image: url('https://example.com/border.png') 30% round;
}

Developer Tools: Inspect Element and View Source

While I will describe the layout of Neospring's pages in the next section, it is still very useful to be able to use developer tools in a browser to find elements on a page, edit them, and view their existing CSS. Developer tools can be daunting to non-technical people due to the complexity of the interface, but you only need to know a few of their features, and they're very similar across browsers.

If you right click on a page and select "Inspect" (or "Inspect Element"), a menu will appear. It should show the HTML code of a page in one view. There, you can select an element, and another part of the menu will show all of the CSS information associated with that element. For convenience, there will usually be a button in the top left corner of the Inspect menu that will allow you to select an element by clicking on it.

You can also view the HTML code of a page by right clicking on it and selecting "View Page Source" (or "Show source code", or something similar.) This will open a new tab that shows only the HTML code, as a text file. One use of this is to find other users' custom CSS; simply use "View Page Source" on a profile page, then use ctrl+F to search for custom_css, and you'll be able to see all of their custom CSS.

If it helps to have a visual, here are annotated screenshots of the Inspect Element menu in Firefox and Chromium.

Neospring-specific information

Neospring, like all webpages, has its own HTML, CSS, and JavaScript code. Your custom CSS is placed very near the end of the page, inside the last of several style tags. This means that your CSS will usually override the CSS that Neospring comes with. However, some of Neospring's CSS uses !important, thus unavoidably overriding your code, and the existing code can interact with yours in unintended ways. Additionally, since you don't have control over the HTML or JavaScript, you need to find the IDs and classes of certain elements in order to develop your theme. This is what makes coding for Neospring different from coding for a personal website.

List of commonly needed selectors

In case you still find Inspect Element daunting, this section will provide a list of useful selectors for elements on the profile page and other pages throughout Neospring, from roughly top to bottom on the profile page. Note that some of these will likely change in the future.

  • body: The entire page.
  • *: Every single element, individually.
  • nav: The navigation bar.
  • button, a.button: Buttons of various types across Neospring.
  • nav button, nav a.button: Buttons on the navbar.
  • .notification: Counters for notifications, inbox questions, and characters in a post.
  • nav .notifcation: Same as above, but only the counters that appear in the navbar.
  • .dropdown button: Buttons to open dropdown menus.
  • .dropdown .inner: Dropdown menus.
  • .avatar: All user avatars.
  • .banner: A profile's banner image.
  • #profile_box: Everything on your profile page aside from the navbar, banner, and footer.
  • .profile_avatar: The large avatar that appears on your profile page.
  • #sidebar: The "sidebar" inside your profile card.
  • #links: The table of links inside your profile card.
  • table tr: The odd-numbered rows of tables, such as the links in the profile card.
  • table tr:nth_child(2n): The even-numbered rows of tables, such as the links in the profile card.
  • #links tr: All of the profile links, individually.
  • #question_box: The question box.
  • .motivational_header: The motivational header of the question box.
  • #content: The text input area inside the question box.
  • .pillmenu: Horizontal sets of buttons such as the one that lets you choose between "Feed" and "Questions", or "Public" and "Following".
  • .card-nest: Question replies, and any other "card"-shaped elements with a header and footer, such as the question box.
  • .card: The "cards" on the page containing posts and similar things.
  • .action_bar: The buttons for replying, liking, etc. in the corner of a post.
  • .camo: Individual buttons within the action bar.
  • button.floating: The search button that floats in the bottom corner of a page.
  • footer: The footer containing links to the about page, terms of service, etc.

Neospring variables

Neospring uses CSS variables to apply your theme colors across the site. If you'd like to apply your theme colors differently to how Neospring does by default, you can use them for CSS's color and background properties, or anywhere else you would place a color or image. Here they are:

  • --color-surface
  • --color-lowered
  • --color-super-lowered
  • --color-raised
  • --color-super-raised
  • --color-text
  • --color-text-raised
  • --color-text-lowered
  • --color-link
  • --color-primary
  • --color-primary-lowered
  • --color-primary-raised
  • --color-text-primary
  • --color-shadow

All should be self explanatory if you've edited your profile colors.

Additionally, there's one hidden variable you might want to change: --radius. This affects the "roundness" of all elements that have rounded borders. The default is 6px. If you'd like completely square borders on an element, include the line --radius: 0; in its CSS.

File hosts known to work with Neospring

One feature of Neospring that can be frustrating to CSS coders is its caching/proxing of images and font files. This means that, when you include an image link in your CSS or your profile settings, Neospring's servers will download the image and replace the link with a link to their copy of the image. Neospring's servers have been blocked by a majority of image hosts due to the frequent automated image downloads that this causes.

You will thus need to upload your images and fonts to one of the few sites that still work:

  • Catbox: Funded by Patreon donations, widely used, and has good policies, but regular downtime.
  • Filegarden: Developed by an anonymous MSPFA community member as a hobby, funded by Patreon donations, has decent uptime and speed.
  • Postimage: An older site created for use with forums, with more features than the other sites. I couldn't find much information on its creator(s) or business model, and I don't know why, but I get "corporate" vibes from it.

All of these options are flawed, and I would not be surprised if any of them stopped working in the future, but Filegarden seems like the best one available.

Additionally, Neospring has been known not to display images or fonts when the URL is in in double quotes (url("https://example.com/image.png")). To prevent this, use single quotes (url('https://example.com/image.png')) or no quotes.

Commented versions of common codes

Here is a list of commonly circulated pieces of CSS code that I've cleaned up, and added comments to explain what they do. Make sure to replace all the placeholders in triangle brackets, but you can leave the comments in for future reference, since CSS ignores them.

Basic font code

/* Font code */

@font-face {
  /* What to call your font */
  font-family: "<name of font>";
  /* Where to find your font */
  src: url('<link to font>');
}

/* Where to use your font */
body {
  /* Which font to use, and a default font in case it doesn't work */
  font-family: "<name of font>", sans-serif; 
}

Scanline effect

/* Scanline effect */

/* Use a "pseudo-element" outside of the page body */
body::before {
  /* No text content */
  content: " ";
  display: block;
  /* Origin of the overlay is in the top left corner */
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  /* How each scanline looks */
  background: linear-gradient(rgb(0 0 0 / 30%) 50%, rgb(0 0 0 / 0%) 50%);
  /* Overlay covers entire page, scanlines repeat */
  background-size: 100% 2px, 3px 100%;
  /* Ensure that it stays above the page */
  z-index: 2;
  /* Ensure that clicking still works */
  pointer-events: none;
}

Profile pic animation

Pulsating

/* Pulsating profile pic */

/* Name of the animation */
@keyframes pulse {
  /* Start at normal size */
  0% {
    transform: scale(1);
  }

  /* Increase in size slightly */ 
  50% {
    transform: scale(1.1);
  }

  /* Return to normal size */
  100% {
    transform: scale(1);
  }
}

/* What to use the animation on */
.profile_avatar {
  /* Use the animation defined earlier, and repeat it every 3 seconds forever */
  animation: pulse 3s ease 1s infinite normal forwards;
}

Floating

/* Floating profile pic */

/* Name of the animation */
@keyframes float {
  /* Start in normal position */
  0% {
    transform: translateY(0px);
  }

  /* Move down 10 pixels */
  50% {
    transform: translateY(-10px);
  }

  /* Return to normal position */
  100% {
    transform: translateY(0px);
  }
}

/* What to use the animation on */
.profile_avatar {
  /* Use the animation defined earlier, and repeat it every 3 seconds forever */
  animation: float 3s ease-in-out infinite;
}

Border image

/* Image border */

/* What to use the border on (be careful what you put here!) */
<selectors> {
  /* Size of the border */
  border: 15px solid;
  /* Where to find the border image, and how to process it */
  /* The "30%" value may need to be adjusted depending
     on the image you use */
  border-image: url(<image URL>) 30% round;
}

Page doll

Variants:

/* Page doll */

/* Use a "pseudo-element" outside of the page body
    (body::before, nav::before, and nav::after would also work) */
body::after {
  /* No text content */
  content: "";
  /* Stays in place as you scroll */
  position: fixed;
  /* Position relative to the edge of the page */
  bottom: 10px;
  <either "right" or "left">: 10px;
  /* Size */
  width: 350px;
  height: 350px;
  /* Where to find the image */
  background-image: url(<image URL>);
  /* How to display the image */
  background-size: contain;
  background-repeat: no-repeat;
  /* Ensure it stays above everything else */
  z-index: 1000;
  /* Ensure it doesn't interfere with interaction with the rest of the page */
  pointer-events: none;
}

Floating version

/* Page doll */

/* Name of the animation */
@keyframes float {
  /* Start in normal position */
  0% {
    transform: translateY(0px);
  }

  /* Move down 20 pixels */
  50% {
    transform: translateY(-20px);
  }

  /* Return to normal position */
  100% {
    transform: translateY(0px);
  }
}

/* Use a "pseudo-element" outside of the page body
    (body::before, nav::before, and nav::after would also work) */
body::after {
  /* Use the animation defined earlier, and repeat it every 3 seconds forever */
  animation: float 3s ease-in-out infinite;
  /* No text content */
  content: "";
  /* Stays in place as you scroll */
  position: fixed;
  /* Position relative to the edge of the page */
  bottom: 10px;
  <either "right" or "left">: 10px;
  /* Size */
  width: 350px;
  height: 350px;
  /* Where to find the image */
  background-image: url(<image URL>);
  /* How to display the image */
  background-size: contain;
  background-repeat: no-repeat;
  /* Ensure it stays above everything else */
  z-index: 1000;
  /* Ensure it doesn't interfere with interaction with the rest of the page */
  pointer-events: none;
}

Hidden on mobile

Large "page dolls" can make navigation difficult on mobile, so this variant only appears on large screens.

/* Page doll */

/* Use a "pseudo-element" outside of the page body
    (body::before, nav::before, and nav::after would also work) */

/* Only show on screens at least 600px wide */
@media screen and (min-width: 600px) {
  body::after {
    /* No text content */
    content: "";
    /* Stays in place as you scroll */
    position: fixed;
   /* Position relative to the edge of the page */
    bottom: 10px;
    <either "right" or "left">: 10px;
    /* Size */
    width: 350px;
    height: 350px;
    /* Where to find the image */
    background-image: url(<image URL>);
    /* How to display the image */
    background-size: contain;
    background-repeat: no-repeat;
    /* Ensure it stays above everything else */
    z-index: 1000;
    /* Ensure it doesn't interfere with interaction with the rest of the page */
    pointer-events: none;
  }
}

Hidden on mobile + floating

/* Page doll */

/* Name of the animation */
@keyframes float {
  /* Start in normal position */
  0% {
    transform: translateY(0px);
  }

  /* Move down 20 pixels */
  50% {
    transform: translateY(-20px);
  }

  /* Return to normal position */
  100% {
    transform: translateY(0px);
  }
}

/* Only show on screens at least 600px wide */
@media screen and (min-width: 600px) {
  /* Use a "pseudo-element" outside of the page body
      (body::before, nav::before, and nav::after would also work) */
  body::after {
    /* Use the animation defined earlier, and repeat it every 3 seconds forever */
    animation: float 3s ease-in-out infinite;
    /* No text content */
    content: "";
    /* Stays in place as you scroll */
    position: fixed;
    /* Position relative to the edge of the page */
    bottom: 10px;
    <either "right" or "left">: 10px;
    /* Size */
    width: 350px;
    height: 350px;
    /* Where to find the image */
    background-image: url(<image URL>);
    /* How to display the image */
    background-size: contain;
    background-repeat: no-repeat;
    /* Ensure it stays above everything else */
    z-index: 1000;
    /* Ensure it doesn't interfere with interaction with the rest of the page */
    pointer-events: none;
  }
}

Custom cursor

1
2
3
4
5
6
7
8
/* Custom cursor */

/* Where your cursor will appear */
* {
  /* Where to find the cursor image or .cur file */
  /*  Without specifying a generic cursor (auto), the browser will refuse to use the URL */
  cursor: url(<image or .cur URL>), auto;
}

Remove notification background

/* Remove notification background */

.notification {
  /* Use the background, color, and font size of the containing element,
     rather than the values set in Neospring's base CSS */
  background: inherit;
  color: inherit;
  font-size: inherit;
}

nav .notification {
  /* Navbar notifications have different styles, so we also need to unset
     the font size there */
  font-size: inherit;
}

Reduce navbar width

1
2
3
4
5
6
7
8
9
/* Reduce navbar width */

/* Apply these styles only if the screen width is at least 600px */
@media (min-width: 600px) {
  nav {
    /* Take up 50% of the screen width */
    width: 50%;
  }
}

Display name typing animation

@keyframes typing {
  /* Start with an empty username */
  from {
    width: 0;
  }
  /* End with a complete username */
  to {
    width: 100%;
  }
}

@keyframes blink {
  /* Remove the border for half of the animation */
  50% {
    border-color: transparent;
  }
}

h3.username {
  /* Hide text before it is typed */
  overflow: hidden;
  /* Ensure the username is always one line */
  white-space: nowrap;
  display: inline-block; /* This property seems superfluous, but I'm keeping it just in case */
  /* 3-pixel wide border, to resemble a typing cursor */
  border-right: 3px solid #000;
  /* Use the typing animation, have it last 3 seconds and animate in 30 steps 
     (rather than smoothly), and also have the blinking animation repeat
     every 0.75 seconds forever, with instant rather than smooth transitions. */
  animation: typing 3s steps(30) 1s forwards, blink 0.75s step-end infinite;
}

Other useful resources

I couldn't possibly give a comprehensive explanation of every CSS feature here, and I've deliberately oversimplified many things, so please search the excellent MDN Web Docs for more specific help. Hopefully this guide has given you enough general information to understand what is written in the docs.

Once again, if you'd like to learn HTML and CSS for personal websites, check out W3Schools.

Permissions

Consider this document public domain, or licensed under the Creative Commons Zero. You are free to share this with whomever you wish, and make your own version with whatever changes you wish, and you don't even need to credit me (but I would very much appreciate it if you did).

Most of the code snippets provided in the "common codes" section were taken from various profiles on Neospring, who in turn copied those codes without credit, so I don't know who originated them. If you can prove that you are the originator of one of those code snippets, and you'd like to be credited or your code removed, let me know on my Neospring.

Edit
Pub: 05 Feb 2025 21:36 UTC
Edit: 20 Feb 2025 22:13 UTC
Views: 1022