
iOS & OS X Style Pure CSS Loading Spinner is a Sass-based CSS loading indicator that recreates the 12-blade activity spinner used in older Apple UI design concepts.
It places the spinner inside a dark overlay for page-level loading states and compiles to regular CSS for the browser.
The animation uses CSS keyframes and staggered delays across 12 .spinner-blade elements. No JavaScript required.
How to use it:
1. Add the spinner markup
The overlay contains one centered spinner and 12 blade elements. Keep all 12 blades because the Sass loop assigns each one a different rotation and animation delay.
<div class="overlay">
<div class="spinner center">
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
<div class="spinner-blade"></div>
</div>
</div>2. Add the Sass styles
The current styles use a 75px spinner, #69717d blades, and a black overlay at 90% opacity. Projects that use plain CSS can compile this Sass once before deployment. No Sass code runs in the browser.
$spinner-size: 75px !default;
$spinner-color: #69717d !default;
$overlay-color: black !default;
@mixin absolute-center {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
.overlay {
background: rgba($overlay-color, .9);
@include absolute-center;
}
.spinner {
font-size: $spinner-size;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
&.center {
@include absolute-center;
}
}
.spinner-blade {
position: absolute;
left: .4629em;
bottom: 0;
width: .074em;
height: .2777em;
border-radius: .5em;
background-color: transparent;
transform-origin: center -.2222em;
animation: spinner-fade 1s infinite linear;
$animation-delay: 0s;
$blade-rotation: 0deg;
@for $i from 1 through 12 {
&:nth-child(#{$i}) {
animation-delay: $animation-delay;
transform: rotate($blade-rotation);
$blade-rotation: $blade-rotation + 30;
$animation-delay: $animation-delay + .083;
}
}
}
@keyframes spinner-fade {
0% {
background-color: $spinner-color;
}
100% {
background-color: transparent;
}
}Customize the spinner
The three Sass variables control the main visual settings. Change them before compiling the stylesheet.
$spinner-sizesets the overall spinner size.$spinner-colorsets the blade color.$overlay-colorsets the overlay color. The.9alpha value controls its opacity.
Change the animation speed
The default animation lasts one second and offsets each blade by .083s, which is approximately one-twelfth of the full cycle. Reduce both values proportionally to keep the 12 blades evenly staggered.
For a faster 0.6-second spinner, use a 0.6-second animation and a 0.05-second delay increment:
.spinner-blade {
animation: spinner-fade .6s infinite linear;
$animation-delay: 0s;
$blade-rotation: 0deg;
@for $i from 1 through 12 {
&:nth-child(#{$i}) {
animation-delay: $animation-delay;
transform: rotate($blade-rotation);
$blade-rotation: $blade-rotation + 30;
$animation-delay: $animation-delay + .05;
}
}
}Keep the overlay fixed to the viewport
The original overlay uses absolute positioning. A positioned ancestor can limit the overlay to that ancestor. For a viewport-level loading screen, override the positioning with position: fixed and inset: 0.
.overlay {
position: fixed;
inset: 0;
}Alternatives:
- iOS Style Loading Spinner with Pure CSS – ispinner
- 10 Best Loading Spinner/Indicator Libraries for Pure CSS & Vanilla JS
- 80+ Best Pure CSS Loading Spinners For Front-end Developers








How do we increase speed?