Author : MD TAREQ HASSAN | Updated : 2020/09/01

For WebAssembly

A simple CSS only spinner (Courtesy: https://codepen.io/Keyamoon/pen/aHxuq)

wwwroot/css/app.css

/* ---------------- Spinner ---------------- */
@font-face {
    font-family: 'icomoon';
    src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: block;
}

[class^="icon-"], [class*=" icon-"] {
    font-family: 'icomoon';
    speak: none;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    /* Better Font Rendering =========== */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.icon-spinner:before {
    content: "\e000";
}

.icon-spinner-2:before {
    content: "\e001";
}

.icon-spinner-3:before {
    content: "\e002";
}

.icon-spinner-4:before {
    content: "\e003";
}

.icon-spinner-5:before {
    content: "\e004";
}

.icon-spinner-6:before {
    content: "\e005";
}

.icon-spinner-7:before {
    content: "\e006";
}

@keyframes anim-rotate {
    0% {
        transform: rotate(0);
    }

    100% {
        transform: rotate(360deg);
    }
}

.spinner {
    display: inline-block;
    font-size: 4em;
    height: 1em;
    line-height: 1;
    margin: .5em;
    animation: anim-rotate 2s infinite linear;
    color: #1cc8ae;
    text-shadow: 0 0 .25em rgba(255,255,255, .3);
}

.spinner--steps {
    animation: anim-rotate 1s infinite steps(8);
}

.spinner--steps2 {
    animation: anim-rotate 1s infinite steps(12);
}

wwwroot/index.html

<!DOCTYPE html>
<html>

<head>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

    <title>BlazorApp</title>

    <base href="/" />
    <link href="manifest.json" rel="manifest" />


    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />


</head>

<body>

    <app>

        <div class="container d-flex min-vh-100">
            <div class="m-auto">
			
                <section class="talign-center bg-transparent">
				
                    <div class="spinner spinner--steps icon-spinner-6" aria-hidden="true"></div>
					
                </section>
				
                <h2 class="text-sm-center">loading...</h2>
            </div>
        </div>

    </app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.webassembly.js"></script>
    <script>navigator.serviceWorker.register('service-worker.js');</script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.bundle.min.js"></script>

    <script>

        $(function () {

        });

    </script>

</body>

</html>

Other CSS only spinner:
See: Loading spinner using css only

A Custom Spinner

Based on

ependencies

Adding spinkit.min.css using libman

libman.json

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "provider": "unpkg",
      "library": "spinkit@2.0.1",
      "destination": "wwwroot",
      "files": [
        "spinkit.min.css"
      ]
    }
  ]
}

Styling spinner: site.css


/* ... ... ... */


/* Spinner realted */
:root {
    --sk-size: 6rem;
    --sk-color: #cf4b00;
}

BuilderExtensions
Components/BuilderExtensions.cs

using System;
using System.Collections.Generic;

namespace Spinner.Server.Components
{

    public struct StyleBuilder
    {
        private string stringBuffer;

        /// <summary>
        /// Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value"></param>
        public static StyleBuilder Default(string prop, string value) => new StyleBuilder(prop, value);

        /// <summary>
        /// Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value"></param>
        public static StyleBuilder Default(string style) => Empty().AddStyle(style);

        /// <summary>
        /// Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string.
        /// </summary>
        public static StyleBuilder Empty() => new StyleBuilder();

        /// <summary>
        /// Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value"></param>
        public StyleBuilder(string prop, string value) => stringBuffer = stringBuffer = $"{prop}:{value};";

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon.
        /// </summary>
        /// <param name="style"></param>
        public StyleBuilder AddStyle(string style) => !string.IsNullOrWhiteSpace(style) ? AddRaw($"{style};") : this;

        /// <summary>
        /// Adds a raw string to the builder that will be concatenated with the next style or value added to the builder.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value"></param>
        /// <returns>StyleBuilder</returns>
        private StyleBuilder AddRaw(string style)
        {
            stringBuffer += style;
            return this;
        }

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value">Style to add</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(string prop, string value) => AddRaw($"{prop}:{value};");

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value">Style to conditionally add.</param>
        /// <param name="when">Condition in which the style is added.</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(string prop, string value, bool when = true) => when ? this.AddStyle(prop, value) : this;


        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value">Style to conditionally add.</param>
        /// <param name="when">Condition in which the style is added.</param>
        /// <returns></returns>
        public StyleBuilder AddStyle(string prop, Func<string> value, bool when = true) => when ? this.AddStyle(prop, value()) : this;

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value">Style to conditionally add.</param>
        /// <param name="when">Condition in which the style is added.</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(string prop, string value, Func<bool> when = null) => this.AddStyle(prop, value, when());

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="value">Style to conditionally add.</param>
        /// <param name="when">Condition in which the style is added.</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(string prop, Func<string> value, Func<bool> when = null) => this.AddStyle(prop, value(), when());

        /// <summary>
        /// Adds a conditional nested StyleBuilder to the builder with separator and closing semicolon.
        /// </summary>
        /// <param name="builder">Style Builder to conditionally add.</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(StyleBuilder builder) => this.AddRaw(builder.Build());

        /// <summary>
        /// Adds a conditional nested StyleBuilder to the builder with separator and closing semicolon.
        /// </summary>
        /// <param name="builder">Style Builder to conditionally add.</param>
        /// <param name="when">Condition in which the style is added.</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(StyleBuilder builder, bool when = true) => when ? this.AddRaw(builder.Build()) : this;

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// </summary>
        /// <param name="builder">Style Builder to conditionally add.</param>
        /// <param name="when">Condition in which the styles are added.</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyle(StyleBuilder builder, Func<bool> when = null) => this.AddStyle(builder, when());

        /// <summary>
        /// Adds a conditional in-line style to the builder with space separator and closing semicolon..
        /// A ValueBuilder action defines a complex set of values for the property.
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="builder"></param>
        /// <param name="when"></param>
        public StyleBuilder AddStyle(string prop, Action<ValueBuilder> builder, bool when = true)
        {
            ValueBuilder values = new ValueBuilder();
            builder(values);
            return AddStyle(prop, values.ToString(), when && values.HasValue);
        }

        /// <summary>
        /// Adds a conditional in-line style when it exists in a dictionary to the builder with separator.
        /// Null safe operation.
        /// </summary>
        /// <param name="additionalAttributes">Additional Attribute splat parameters</param>
        /// <returns>StyleBuilder</returns>
        public StyleBuilder AddStyleFromAttributes(IReadOnlyDictionary<string, object> additionalAttributes) =>
            additionalAttributes == null ? this :
            additionalAttributes.TryGetValue("style", out var c) ? AddRaw(c.ToString()) : this;


        /// <summary>
        /// Finalize the completed Style as a string.
        /// </summary>
        /// <returns>string</returns>
        public string Build()
        {
            // String buffer finalization code
            return stringBuffer != null ? stringBuffer.Trim() : string.Empty;
        }

        // ToString should only and always call Build to finalize the rendered string.
        public override string ToString() => Build();
    }

    public class ValueBuilder
    {
        private string stringBuffer;

        public bool HasValue => !string.IsNullOrWhiteSpace(stringBuffer);
        /// <summary>
        /// Adds a space separated conditional value to a property.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="when"></param>
        /// <returns></returns>
        public ValueBuilder AddValue(string value, bool when = true) => when ? AddRaw($"{value} ") : this;
        public ValueBuilder AddValue(Func<string> value, bool when = true) => when ? AddRaw($"{value()} ") : this;

        private ValueBuilder AddRaw(string style)
        {
            stringBuffer += style;
            return this;
        }

        public override string ToString() => stringBuffer != null ? stringBuffer.Trim() : string.Empty;
    }

    public struct CssBuilder
    {
        private string stringBuffer;

        /// <summary>
        /// Creates a CssBuilder used to define conditional CSS classes used in a component.
        /// Call Build() to return the completed CSS Classes as a string. 
        /// </summary>
        /// <param name="value"></param>
        public static CssBuilder Default(string value) => new CssBuilder(value);

        /// <summary>
        /// Creates an Empty CssBuilder used to define conditional CSS classes used in a component.
        /// Call Build() to return the completed CSS Classes as a string. 
        /// </summary>
        public static CssBuilder Empty() => new CssBuilder();

        /// <summary>
        /// Creates a CssBuilder used to define conditional CSS classes used in a component.
        /// Call Build() to return the completed CSS Classes as a string. 
        /// </summary>
        /// <param name="value"></param>
        public CssBuilder(string value) => stringBuffer = value;

        /// <summary>
        /// Adds a raw string to the builder that will be concatenated with the next class or value added to the builder.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddValue(string value)
        {
            stringBuffer += value;
            return this;
        }

        /// <summary>
        /// Adds a CSS Class to the builder with space separator.
        /// </summary>
        /// <param name="value">CSS Class to add</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(string value) => AddValue(" " + value);

        /// <summary>
        /// Adds a conditional CSS Class to the builder with space separator.
        /// </summary>
        /// <param name="value">CSS Class to conditionally add.</param>
        /// <param name="when">Condition in which the CSS Class is added.</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(string value, bool when = true) => when ? this.AddClass(value) : this;

        /// <summary>
        /// Adds a conditional CSS Class to the builder with space separator.
        /// </summary>
        /// <param name="value">CSS Class to conditionally add.</param>
        /// <param name="when">Condition in which the CSS Class is added.</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(string value, Func<bool> when = null) => this.AddClass(value, when());

        /// <summary>
        /// Adds a conditional CSS Class to the builder with space separator.
        /// </summary>
        /// <param name="value">Function that returns a CSS Class to conditionally add.</param>
        /// <param name="when">Condition in which the CSS Class is added.</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(Func<string> value, bool when = true) => when ? this.AddClass(value()) : this;

        /// <summary>
        /// Adds a conditional CSS Class to the builder with space separator.
        /// </summary>
        /// <param name="value">Function that returns a CSS Class to conditionally add.</param>
        /// <param name="when">Condition in which the CSS Class is added.</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(Func<string> value, Func<bool> when = null) => this.AddClass(value, when());

        /// <summary>
        /// Adds a conditional nested CssBuilder to the builder with space separator.
        /// </summary>
        /// <param name="value">CSS Class to conditionally add.</param>
        /// <param name="when">Condition in which the CSS Class is added.</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(CssBuilder builder, bool when = true) => when ? this.AddClass(builder.Build()) : this;

        /// <summary>
        /// Adds a conditional CSS Class to the builder with space separator.
        /// </summary>
        /// <param name="value">CSS Class to conditionally add.</param>
        /// <param name="when">Condition in which the CSS Class is added.</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClass(CssBuilder builder, Func<bool> when = null) => this.AddClass(builder, when());

        /// <summary>
        /// Adds a conditional CSS Class when it exists in a dictionary to the builder with space separator.
        /// Null safe operation.
        /// </summary>
        /// <param name="additionalAttributes">Additional Attribute splat parameters</param>
        /// <returns>CssBuilder</returns>
        public CssBuilder AddClassFromAttributes(IReadOnlyDictionary<string, object> additionalAttributes) =>
            additionalAttributes == null ? this :
            additionalAttributes.TryGetValue("class", out var c) ? AddClass(c.ToString()) : this;

        /// <summary>
        /// Finalize the completed CSS Classes as a string.
        /// </summary>
        /// <returns>string</returns>
        public string Build()
        {
            // String buffer finalization code
            return stringBuffer != null ? stringBuffer.Trim() : string.Empty;
        }

        // ToString should only and always call Build to finalize the rendered string.
        public override string ToString() => Build();

    }

    public static class BuilderExtensions
    {
        /// <summary>
        /// Used to convert a CssBuilder into a null when it is empty.
        /// Usage: class=null causes the attribute to be excluded when rendered.
        /// </summary>
        /// <param name="builder"></param>
        /// <returns>string</returns>
        public static string NullIfEmpty(this CssBuilder builder) =>
            string.IsNullOrEmpty(builder.ToString()) ? null : builder.ToString();

        /// <summary>
        /// Used to convert a StyleBuilder into a null when it is empty.
        /// Usage: style=null causes the attribute to be excluded when rendered.
        /// </summary>
        /// <param name="builder"></param>
        /// <returns>string</returns>
        public static string NullIfEmpty(this StyleBuilder builder) =>
            string.IsNullOrEmpty(builder.ToString()) ? null : builder.ToString();

        /// <summary>
        /// Used to convert a string.IsNullOrEmpty into a null when it is empty.
        /// Usage: attribute=null causes the attribute to be excluded when rendered.
        /// </summary>
        /// <param name="builder"></param>
        /// <returns>string</returns>
        public static string NullIfEmpty(this string s) =>
            string.IsNullOrEmpty(s) ? null : s;

    }
}

SpinnerType
Components/SpinnerType.cs

namespace Spinner.Server.Components
{
    public enum SpinnerType
    {
        Circle,
        CircleFade
    }
}

SpinnerBase
Components/SpinnerBase.cs

using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Spinner.Server.Components
{
    public class SpinnerBase : ComponentBase
    {
        [Parameter] public string Color { get; set; }
        [Parameter] public bool Center { get; set; }
        [Parameter] public string Size { get; set; }

        [Parameter(CaptureUnmatchedValues = true)]
        public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, Object>();

    }
}

SpinnerContainer
Components/SpinnerContainer.razor

@namespace Spinner.Server.Components
@inherits SpinnerBase
<div @attributes="AdditionalAttributes" class="@CssClass" style="@Style">
    @ChildContent
</div>

@code {

    [Parameter] public RenderFragment ChildContent { get; set; }
    [Parameter] public string BaseCss { get; set; }
    string CssClass =>
        CssBuilder.Default(BaseCss)
        .AddClass("sk-center", Center)
        .AddClassFromAttributes(AdditionalAttributes)
    .Build();

    string Style =>
        StyleBuilder.Empty()
        .AddStyle("--sk-color", Color, when: !string.IsNullOrEmpty(Color))
        .AddStyle("--sk-size", Size, when: !string.IsNullOrEmpty(Size))
        .AddStyleFromAttributes(AdditionalAttributes)
    .NullIfEmpty();
}

Spinner type implementations

Components/Circle.razor

@namespace Spinner.Server.Components
@inherits SpinnerBase

<SpinnerContainer Center="@Center" Color="@Color" Size="@Size" @attributes="AdditionalAttributes" BaseCss="sk-circle">
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
    <div class="sk-circle-dot"></div>
</SpinnerContainer>

Components/CircleFade.razor

@namespace Spinner.Server.Components
@inherits SpinnerBase

<SpinnerContainer Center="@Center" Color="@Color" Size="@Size" @attributes="AdditionalAttributes" BaseCss="sk-chase">
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
    <div class="sk-circle-fade-dot"></div>
</SpinnerContainer>

DefaultSpinnerChooser
Components/DefaultSpinnerChooser.razor

@namespace Spinner.Server.Components
@inherits SpinnerBase

@switch (DefaultSpinner)
{
    case SpinnerType.Circle:
        <Circle Center="@Center" Size="@Size" Color="@Color" @attributes="AdditionalAttributes" />
        break;
    case SpinnerType.CircleFade:
        <CircleFade Center="@Center" Size="@Size" Color="@Color" @attributes="AdditionalAttributes" />
        break;
    default:
        <CircleFade Center="@Center" Size="@Size" Color="@Color" @attributes="AdditionalAttributes" />
        break;
}

@code {
    [Parameter] public SpinnerType DefaultSpinner { get; set; }
}

SpinLoader
Components/SpinLoader.razor

@inherits SpinnerBase

@if (IsLoading)
{
    if (LoadingTemplate == null)
    {
        <div class="row" style="min-height: 50rem;">
            <div class="col-sm-12 my-sm-auto">
                <CircleFade Center="true" />
            </div>
        </div>
    }
    else
    {
        @LoadingTemplate
    }
}
else
{
    if (!IsFaulted || FaultedContentTemplate == null)
    {
        @ContentTemplate
    }
    if (IsFaulted)
    {
        @FaultedContentTemplate
    }
}

@code {

    [Parameter] public SpinnerType Spinner { get; set; }

    [Parameter] public bool IsLoading { get; set; }

    [Parameter] public bool IsFaulted { get; set; }

    [Parameter] public RenderFragment LoadingTemplate { get; set; }

    [Parameter] public RenderFragment ContentTemplate { get; set; }

    [Parameter] public RenderFragment FaultedContentTemplate { get; set; }

}

Usage

Pages/FetchData.razor

@page "/fetchdata"

@using Spinner.Server.Data
@inject WeatherForecastService ForecastService


<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

<SpinLoader IsLoading="@(forecasts == null)">
    <ContentTemplate>
        <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var forecast in forecasts)
                {
                    <tr>
                        <td>@forecast.Date.ToShortDateString()</td>
                        <td>@forecast.TemperatureC</td>
                        <td>@forecast.TemperatureF</td>
                        <td>@forecast.Summary</td>
                    </tr>
                }
            </tbody>
        </table>
    </ContentTemplate>
</SpinLoader>


@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(2000);
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

Better Way To Use Custom Spinner

Components/BaseComponent.cs

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.JSInterop;
using Serilog;
using System;
using System.Linq;
using System.Threading.Tasks;

    public class BaseComponent: ComponentBase, IDisposable
    {

        [Inject]
        protected NavigationManager NavigationManager { get; set; }

        [Inject]
        protected IJSRuntime JSRuntime { get; set; }

        protected string ErroMessage { get; set; } = string.Empty;
		
		// ... ... ...

        protected bool ShowSpinner { get; set; } = true;


        public void Dispose()
        {
            ShowSpinner = false;
        }
		
		// ... ... ...
    }

Pages/Index.razor.cs

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Serilog;
using Microsoft.AspNetCore.WebUtilities;
using System.Linq;


namespace Server.Spinner.Pages
{
    public class IndexBase : BaseComponent
    {

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            //
            // Lifecycle callback method will be called twice => use firstRender flag
            //
            if (firstRender)
            {
                ErroMessage = string.Empty;

                try
                {
                    // ... ... ...
					
					// DB operation here (async await)

                }
                catch (AppException ex) // logging will be done in service
                {
                    ErroMessage = ex.Message;
                }
                catch (Exception ex) // do logging
                {
                    Log.Error(ex.Message, ex);
					
                    ErroMessage = "Unexpected error occured!";
                }

                ShowSpinner = !ShowSpinner;
				
                StateHasChanged();
            }
        }

    }
}

Pages/Index.razor

@page "/"
@page "/items"
@inherits IndexBase


<SpinLoader IsLoading="@ShowSpinner">

    <ContentTemplate>
	
        <div class="row ">
			<!-- ... ... ... -->
        </div>

        <div class="row">
			<!-- ... ... ... -->
        </div>
		
    </ContentTemplate>

</SpinLoader>