A proper mad science laboratory requires mysterious potion-filled test tubes lit with eerie colors to flicker in thier rack. Build this display for maximum creepy effect!

Flourescent pigments mixed with water make for a creepy-cool luminescent potion when lit with ultraviolet light, especially as they flicker and blink based on a microcontroller pattern you design.

You can use this technique to make test tubes glow in a standard test tube rack, or build your own from black iron pipe fittings and laser-cut acrylic for some next level spookiness!

Here's a very nice user-submitted Menorah version.

Don't have any UV pigment? No problem -- get some tonic water instead! The quinine in tonic water fluoresces a cool blue color under UV light.

Lighted Test Tube Parts


Materials and Tools

  • Soldering iron
  • Solder
  • Wire strippers
  • Heat shrink tubing

Optional Test Tube Rack Parts

  • 2 ea. 1/2" threaded pipe nipple, 6" length
  • 4 ea. 1/2" threaded pipe nipple, 8" length
  • 2 ea. 1/2" threaded pipe fitting, 90 degree elbow
  • 3 ea. 1/2" threaded pipe fitting, coupling
  • 4 ea. 1/2" threaded pipe fitting, locknut
  • 2 ea. 1/2" threaded pipe floor flange

Optional Tools

  • Drill with 1/8" bit for pilot hole
  • Screwdriver

You'll use the Feather microcontroller to drive up to thirteen purple UV LEDs. Ultraviolet light causes the special pigments to fluoresce, or emit light of a longer wavelength when lit. This is the same effect you may have seen on a blacklight poster.

Fluorescence is different from the phosphorescence of glowing pigments, where light emission continues after the initial source of light has been removed.

Assemble the TerminalBlock FeatherWing according to these instructions, then fit the Feather Basic Proto onto it. You'll use the screw terminals to make the wiring easy and secure.

Connect the Feather via USB to your computer and upload the code via Arduino IDE.

// ---------------------------------------------------------------------------
//Mad Science Test Tube Rack
// John Park
// for Adafruit Industries
// Blinks thirteen LEDs, each in their own timing pattern.
// Designed for use with the Adafruit Feather 32u4 and Feather M0, 
// but will work with most Arduino boards.
// MIT license, check LICENSE for more information
// ---------------------------------------------------------------------------

//Output pins
byte LED_PINS[13] = {A0, A1, A2, A3, A4, A5, 5, 6, 9, 10, 11, 12, 13};

//Timing patterns: 
//Each row in is the timing in millis of a single LED
//Adjust these to tune the timing
unsigned int TIMING[][13] = {
  {random(4500, 8500), 6000, 12000, random(4000, 6000), random (10000, 20000)},
  {3500, random (1000, 5000), 14000, 500, 6500, 800, 4000},
  {5850, 800, 3500, random (3000, 10000)},
  {random(500, 3000), random (1000,3000), 700, 2000, 9000},
  {6500, 600, 3000, random (3000, 7000), 7000, 30000},
  {4000, random(1000, 7000), 4000, 5000, 4500},
  {7000, 800, 5000, random(2000, 4000)},
  {random(5000, 8000), 500, random(7000, 25000), 200, random(1000, 8000), 3000, 500, random(5000, 9000)},
  {random(300, 1000), random (1000, 7000), 1400, 500, 3500},
  {random (2500, 3500), 800, 18050, random(1000, 20000)},
  {13000, 1000, random (300, 2000), 2000, random (500, 4000)},
  {15000, random (500, 7000), random(1000, 4000), 5000, random (1000, 1500)},
  {random(2000, 6000), 800, 3500, random (500, 2500), 1000, 300, 400, 500, random (200, 18000)}

// Keep track of timing sequence
unsigned long last_change[sizeof(LED_PINS)/sizeof(LED_PINS[0])];
byte timing_i[sizeof(LED_PINS)/sizeof(LED_PINS[0])];

void setup() {
  //Set all 13 pins to output mode and turn on
  for (byte i = 0; i < sizeof(LED_PINS)/sizeof(LED_PINS[0]); i++) {
    pinMode(LED_PINS[i], OUTPUT);
    digitalWrite(LED_PINS[i], HIGH);

void loop() {
  unsigned long now = millis(); // Current time

  // Keep track of pattern for each LED
  for (byte i = 0; i < sizeof(LED_PINS)/sizeof(LED_PINS[0]); i++) {
    if (now - last_change[i] >= TIMING[i][timing_i[i]]) {
      digitalWrite(LED_PINS[i], !digitalRead(LED_PINS[i]));
      timing_i[i] %= sizeof(TIMING[i])/sizeof(TIMING[i][0]); // Repeat pattern when done
      last_change[i] = now;

Next, you'll wire the LEDs to the TerminalBlock FeatherWing. Depending on the number of test tubes you use, the wiring may vary, but this scheme allows for one LED per output pin on the Feather (a total of thirteen) and uses the four GND terminals to best effect.

You will normally use current limiting resistors to keep LEDs from burning out, but the 3.3V source of the Feather is so close to the forward voltage of these LEDs that you can get away without a resistor. Be sure to add a proper resister if using a higher voltage board.

Prep your wires by cutting pairs of wires for each test tube LED you plan to use, approximately 10" in length each. In the version made for this guide there are thirteen of them. We can use one Feather output pin per LED, but we don't have thirteen GND pins, so we'll create a few short wire splices to accomodate.

Strip the ends of each wire, and trim the cloth if using a fabric covered wire. You may also use short lengths of heat shrink tubing to keep the cloth from fraying or wires from shorting.

Slip a bit of heat shrink tubing over the end of each wire and then push the LEDs through a 1x2 jumper houseing (this is optional, it just keeps the legs from shorting and looks neat) solder a wire to each leg of the LEDs. Be sure to keep track of polarity -- you can use a multimeter's continuity testing mode to be certain -- and then screw each anode (long leg, +) wire into an output pin on the TerminalBlock FeatherWing. The thirteen output pins we'll use are 5, 6, 9-13, and A0-A5.

Next, create the ground cables to share among the LEDs. Solder two short wires (optionally with 1x1 female jumper sockets on one end) to the ground wire.

Solder this ground splitter to the cathode (short) leg of one LED, then you will be able to share this ground with the two short leads to two other LEDs.

Place a length of heat shrink tubing over the positive wire before soldering to the anode (longer) leg of the LED. Solder the wire to the leg, then slide the heat shrink tubing over it and heat it to shrink.

Black iron gas pipe and fittings are relatively inexpensive, strong, and modular, which can make them ideal for fabricating sturdy props and mounts. The parts are readily available at any hardware store.

Black pipe has a thin coating of grease and paint on it, which usually comes of all over your hands, clothing, and work area if you aren't careful. Use degreaser, such as Simple Green, and some old rags to clean it before use.

This rack is optional -- you may use a normal test tube rack if you like -- but either way, it helps to have a rack before you begin mixing your potion and filling the test tubes!

Screw two of the 8" pipe nipples into the floor flanges.

Screw two of the pipe couplings onto the two pipe nipples that have been screwed into the flanges.

Use the CAD file linked below to laser cut the acrylic rack shelves and support. Alternately, you may print the file and use it as a template to cut the rack with a scroll saw.

Screw one locknut onto each end of the remaining two 8" pipe nipples. Set the bottom rack shelf onto the bottom ends of the pipe nipples and then thread these into the pipe fittings.

Place the acrylic support that will hold the Feather into the base and top shelves, fitting its pointed end into the square cutout.

Create the crossbar by joining the two 6" pipe nipples with the remaining coupling, then screw their free ends into the elbows. 

Place the top shelf over the leg threads, being sure to line up the vertical support with the square cutout, and then screw the legs into the elbows.

To stabilize the rack, you can screw the flanges into a wooden base.

It's mixing time. Be careful when working with the pigment not to breathe any of the very fine powder (wear a mask or respirator), and work on a surface that can be cleaned, such as a wax paper covered workbench.

Wear nitrile gloves to keep the dye off of your hands, and mark your containers as "not for consumption"

It only takes a very small pinch of powder per test tube. You can mix the pigment and water in each test tube -- use a stirring rod or bamboo skewer to thoroughly mix it, or place a stopper over the top, cover the hole with your thumb and shake it like crazy.

If you plan to use the same color in a number of test tubes, premix a batch in a measuring cup, mason jar, or beaker and then pour 50mL per test tube.

Shine a UV LED into your tube to see the effect! 

If there's too much pigment, the tube will not glow internally, only on the surface. In this case, add water to dilute your mixture.

The pigment is suspended in the water, so you may need to occassionally shake it up to redistribute, or mix in a tiny amount of an emulsifier such as xanthan gum (available in health food stores and online) to keep it in suspension.

As an alternative to pigments, the ink from many highlighters is also UV reactive.

You can now attach the FeatherWing to the vertical support of the laser cut acrylic using the M3 standoffs, screws, and nuts. 

If you're using a different stand, you'll want to attach the Feather using another method, such as drilling small holes in the wood, or affixing with double-stick foam tape.

Place a standoff in each of the four holes, then affix a nut to the back side.

Place the FeatherWing over the four standoffs and secure with the four screws.

Attach the LED wiring to the screw terminals of the TerminalBlock FeatherWing, using this circuit diagram as reference.

With a potion-filled test tube in each slot of your rack, push a UV LED into the hole of each stopper and then place the stoppers in the test tubes.

Connect the Feather to the wall power adapter using the USB cable, and then plug the adapter into a wall outlet.

Now, turn down the lights, turn the FeatherWing power switch to ON and enjoy the spooky, mad science light show!

This guide was first published on Oct 17, 2016. It was last updated on Mar 08, 2024.