diff --git a/src/main/java/bdv/viewer/ViewerPanel.java b/src/main/java/bdv/viewer/ViewerPanel.java
index 444d4adfb16b6da95aab374d7e56fcda5a334e8a..80cca1688e812848bcc25ef2bcde7d6339d57dce 100644
--- a/src/main/java/bdv/viewer/ViewerPanel.java
+++ b/src/main/java/bdv/viewer/ViewerPanel.java
@@ -45,6 +45,7 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.LayoutManager;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ComponentAdapter;
@@ -72,6 +73,7 @@ import javax.swing.JPanel;
 import javax.swing.JSlider;
 import javax.swing.Timer;
 import javax.swing.border.EmptyBorder;
+import javax.swing.SwingConstants;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 
@@ -170,7 +172,7 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 	 */
 	protected final InteractiveDisplayCanvasComponent< AffineTransform3D > display;
 
-	protected final JSlider sliderTime;
+	protected final TimeSlider sliderTime;
 
 	/**
 	 * A {@link ThreadGroup} for (only) the threads used by this
@@ -427,10 +429,10 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 		sliderPanel.add( timeKeyframePanel );
 		timeKeyframePanel.setLayout( new BoxLayout( timeKeyframePanel, BoxLayout.Y_AXIS ) );
 
-		sliderTime = new JSlider( 0, numTimepoints - 1, 0 );
-//		sliderTime.setMinimumSize( new Dimension( 36, 26 ) );
-//		sliderTime.setMaximumSize( new Dimension( 32767, 26 ) );
-		sliderTime.setSnapToTicks( true );
+
+		sliderTime = new DefaultTimeSlider();
+		sliderTime.updateNumTimepoints( 0, numTimepoints );
+		sliderTime.addTimePointListener( this::setTimepoint );
 
 		timeKeyframePanel.add( sliderTime );
 
@@ -439,16 +441,6 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 		keyframePanel.setPreferredSize( new Dimension( 36, 26 ) );
 		timeKeyframePanel.add( keyframePanel );
 
-		sliderTime.addChangeListener( new ChangeListener()
-		{
-			@Override
-			public void stateChanged( final ChangeEvent e )
-			{
-				if ( e.getSource().equals( sliderTime ) )
-					setTimepoint( sliderTime.getValue() );
-			}
-		} );
-
 		add( display, BorderLayout.CENTER );
 		if ( numTimepoints > 1 )
 			add( sliderPanel, BorderLayout.SOUTH );
@@ -886,12 +878,10 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 	 */
 	public synchronized void setTimepoint( final int timepoint )
 	{
-		if ( state.getCurrentTimepoint() != timepoint )
+		if ( state.getCurrentTimepoint() != timepoint && timepoint >= 0 && timepoint < state.getNumTimepoints() )
 		{
 			state.setCurrentTimepoint( timepoint );
-			sliderTime.setValue( timepoint );
-			for ( final TimePointListener l : timePointListeners )
-				l.timePointChanged( timepoint );
+			sliderTime.setTimepoint( timepoint );
 			requestRepaint();
 		}
 	}
@@ -902,7 +892,7 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 	public synchronized void nextTimePoint()
 	{
 		if ( state.getNumTimepoints() > 1 )
-			sliderTime.setValue( sliderTime.getValue() + 1 );
+			setTimepoint( state.getCurrentTimepoint() + 1 );
 	}
 
 	/**
@@ -911,7 +901,7 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 	public synchronized void previousTimePoint()
 	{
 		if ( state.getNumTimepoints() > 1 )
-			sliderTime.setValue( sliderTime.getValue() - 1 );
+			setTimepoint( state.getCurrentTimepoint() - 1 );
 	}
 
 	/**
@@ -952,7 +942,7 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 			for ( final TimePointListener l : timePointListeners )
 				l.timePointChanged( timepoint );
 		}
-		sliderTime.setModel( new DefaultBoundedRangeModel( state.getCurrentTimepoint(), 0, 0, numTimepoints - 1 ) );
+		sliderTime.updateNumTimepoints( state.getCurrentTimepoint(), numTimepoints );
 		revalidate();
 		requestRepaint();
 	}
@@ -1348,4 +1338,78 @@ public class ViewerPanel extends JPanel implements OverlayRenderer, TransformLis
 			return t;
 		}
 	}
+
+	// TODO: move to separate file, add to ViewerOptions
+	public static abstract class TimeSlider extends JPanel
+	{
+		public TimeSlider( final LayoutManager layout, final boolean isDoubleBuffered )
+		{
+			super( layout, isDoubleBuffered );
+		}
+
+		public TimeSlider( final LayoutManager layout )
+		{
+			super( layout );
+		}
+
+		public TimeSlider( final boolean isDoubleBuffered )
+		{
+			super( isDoubleBuffered );
+		}
+
+		public TimeSlider()
+		{
+			super();
+		}
+
+		public abstract void updateNumTimepoints( int currentTimepoint, int numTimepoints );
+
+		public abstract void setTimepoint( int timepoint );
+
+		public abstract void addTimePointListener( TimePointListener listener );
+	}
+
+	// TODO: move to separate file
+	public static class DefaultTimeSlider extends TimeSlider
+	{
+		private final CopyOnWriteArrayList< TimePointListener > listeners;
+
+		private final JSlider slider;
+
+		public DefaultTimeSlider()
+		{
+			super( new BorderLayout() );
+			listeners = new CopyOnWriteArrayList<>();
+			slider = new JSlider( SwingConstants.HORIZONTAL );
+			slider.addChangeListener( new ChangeListener()
+			{
+				@Override
+				public void stateChanged( final ChangeEvent e )
+				{
+					for ( final TimePointListener l : listeners )
+						l.timePointChanged( slider.getValue() );
+				}
+			} );
+			slider.setSnapToTicks( true );
+			add( slider, BorderLayout.CENTER );
+		}
+
+		@Override
+		public void updateNumTimepoints( final int currentTimepoint, final int numTimepoints )
+		{
+			slider.setModel( new DefaultBoundedRangeModel( currentTimepoint, 0, 0, numTimepoints - 1 ) );
+		}
+
+		@Override
+		public void setTimepoint( final int timepoint )
+		{
+			slider.setValue( timepoint );
+		}
+
+		@Override
+		public synchronized void addTimePointListener( final TimePointListener listener )
+		{
+			listeners.add( listener );
+		}
+	}
 }