diff --git a/docs/releases/v2.11.0.md b/docs/releases/v2.11.0.md
index 66b299e4f20eb350124565f90f434fc437956519..548e2e064c8be5d708a744b7fa0d682b12c45e85 100644
--- a/docs/releases/v2.11.0.md
+++ b/docs/releases/v2.11.0.md
@@ -5,6 +5,7 @@
 - Automatically selects human multilevel atlas on startup, if no atlases are selected
 - Allow multiple region selection for visualization purposes (`<ctrl>` + `[click]` on region)
 - Allow point to be selected (`[right click]`) and enabling map assignment
+- Allow atlas download
 
 ## Behind the scenes
 
diff --git a/e2e/util/selenium/layout.js b/e2e/util/selenium/layout.js
index 2b102b6245845b4ec55a57d7b99df3c249292381..27f1f6e15d88cb4afa500609f1c49e62c7ad586b 100644
--- a/e2e/util/selenium/layout.js
+++ b/e2e/util/selenium/layout.js
@@ -600,11 +600,6 @@ class WdLayoutPage extends WdBase{
       .findElement( By.css('[aria-label="Show pinned datasets"]') )
   }
 
-  async getNumberOfFavDataset(){
-    const attr = await this._getFavDatasetIcon().getAttribute('pinned-datasets-length')
-    return Number(attr)
-  }
-
   async showPinnedDatasetPanel(){
     await this._getFavDatasetIcon().click()
     await this.wait(500)
diff --git a/src/atlas-download/atlas-download.directive.spec.ts b/src/atlas-download/atlas-download.directive.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e81fe2966b376e21eded2b6ec96a79a85faaf616
--- /dev/null
+++ b/src/atlas-download/atlas-download.directive.spec.ts
@@ -0,0 +1,9 @@
+import { NEVER } from 'rxjs';
+import { AtlasDownloadDirective } from './atlas-download.directive';
+
+describe('AtlasDownloadDirective', () => {
+  it('should create an instance', () => {
+    const directive = new AtlasDownloadDirective(NEVER as any);
+    expect(directive).toBeTruthy();
+  });
+});
diff --git a/src/atlas-download/atlas-download.directive.ts b/src/atlas-download/atlas-download.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03629a5e09e189e09397b833840cee1b8093ce3a
--- /dev/null
+++ b/src/atlas-download/atlas-download.directive.ts
@@ -0,0 +1,99 @@
+import { Directive, HostListener } from '@angular/core';
+import { Store, select } from '@ngrx/store';
+import { Subject, concat, of } from 'rxjs';
+import { distinctUntilChanged, shareReplay, take } from 'rxjs/operators';
+import { SAPI } from 'src/atlasComponents/sapi';
+import { MainState } from 'src/state';
+import { fromRootStore, selectors } from "src/state/atlasSelection"
+import { wait } from "src/util/fn"
+
+@Directive({
+  selector: '[sxplrAtlasDownload]',
+  exportAs: 'atlasDlDct'
+})
+export class AtlasDownloadDirective {
+
+  @HostListener('click')
+  async onClick(){
+    try {
+
+      this.#busy$.next(true)
+      const { parcellation, template } = await this.store.pipe(
+        fromRootStore.distinctATP(),
+        take(1)
+      ).toPromise()
+
+      const selectedRegions = await this.store.pipe(
+        select(selectors.selectedRegions),
+        take(1)
+      ).toPromise()
+
+      const endpoint = await SAPI.BsEndpoint$.pipe(
+        take(1)
+      ).toPromise()
+
+      const url = new URL(`${endpoint}/atlas_download`)
+      const query = {
+        parcellation_id: parcellation.id,
+        space_id: template.id,
+      }
+      if (selectedRegions.length === 1) {
+        query['region_id'] = selectedRegions[0].name
+      }
+      for (const key in query) {
+        url.searchParams.set(key, query[key])
+      }
+  
+      const resp = await fetch(url)
+      const { task_id } = await resp.json()
+  
+      if (!task_id) {
+        throw new Error(`Task id not found`)
+      }
+      const pingUrl = new URL(`${endpoint}/atlas_download/${task_id}`)
+      while (true) {
+        await wait(320)
+        const resp = await fetch(pingUrl)
+        if (resp.status >= 400) {
+          throw new Error(`task id thrown error ${resp.status}, ${resp.statusText}, ${resp.body}`)
+        }
+        const { status } = await resp.json()
+        if (status === "SUCCESS") {
+          break
+        }
+      }
+
+      /**
+       * n.b. this *needs* to happen in the same invocation chain from when click happened
+       * modern browser is pretty strict on what can and cannot 
+       */
+      const anchor = document.createElement('a')
+      anchor.href = `${endpoint}/atlas_download/${task_id}/download`
+      anchor.target = "_blank"
+      anchor.download = "download.zip"
+      document.body.appendChild(anchor)
+      anchor.click()
+      document.body.removeChild(anchor)
+      this.#busy$.next(false)
+    } catch (e) {
+      this.#busy$.next(false)
+      this.#error$.next(e.toString())
+    }
+    
+  }
+
+  #busy$ = new Subject<boolean>()
+  busy$ = concat(
+    of(false),
+    this.#busy$,
+  ).pipe(
+    distinctUntilChanged(),
+    shareReplay(1)
+  )
+
+  #error$ = new Subject<string>()
+  error$ = this.#error$.pipe()
+
+  constructor(private store: Store<MainState>) { }
+
+}
diff --git a/src/atlas-download/atlas-download.module.ts b/src/atlas-download/atlas-download.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b4181a9cc0526254f2cabd6fbe1aa7b9894acfc8
--- /dev/null
+++ b/src/atlas-download/atlas-download.module.ts
@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { AtlasDownloadDirective } from './atlas-download.directive';
+
+
+@NgModule({
+  declarations: [
+    AtlasDownloadDirective
+  ],
+  imports: [
+    CommonModule,
+  ],
+  exports: [
+    AtlasDownloadDirective
+  ]
+})
+export class AtlasDownloadModule { }
diff --git a/src/atlasComponents/sapi/schemaV3.ts b/src/atlasComponents/sapi/schemaV3.ts
index 41ea4e33a0d995c722403fb5d0027b2289c91d1f..de27935f67b5a381ef1d04069f41fad1733a6b6e 100644
--- a/src/atlasComponents/sapi/schemaV3.ts
+++ b/src/atlasComponents/sapi/schemaV3.ts
@@ -73,6 +73,14 @@ export interface paths {
     /** Router Assign Point */
     get: operations["router_assign_point_map_assign_get"]
   }
+  "/atlas_download": {
+    /** Prepare Download */
+    get: operations["prepare_download_atlas_download_get"]
+  }
+  "/atlas_download/{task_id}": {
+    /** Get Task Id */
+    get: operations["get_task_id_atlas_download__task_id__get"]
+  }
   "/feature/_types": {
     /** Get All Feature Types */
     get: operations["get_all_feature_types_feature__types_get"]
@@ -498,8 +506,6 @@ export interface components {
       "@type": string
       /** Index */
       index: unknown[]
-      /** Dtype */
-      dtype: string
       /** Columns */
       columns: unknown[]
       /** Ndim */
@@ -1697,6 +1703,52 @@ export interface operations {
       }
     }
   }
+  prepare_download_atlas_download_get: {
+    /** Prepare Download */
+    parameters: {
+      query: {
+        space_id: string
+        parcellation_id: string
+        region_id?: string
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      200: {
+        content: {
+          "application/json": Record<string, never>
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
+  get_task_id_atlas_download__task_id__get: {
+    /** Get Task Id */
+    parameters: {
+      path: {
+        task_id: string
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      200: {
+        content: {
+          "application/json": Record<string, never>
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        content: {
+          "application/json": components["schemas"]["HTTPValidationError"]
+        }
+      }
+    }
+  }
   get_all_feature_types_feature__types_get: {
     /** Get All Feature Types */
     parameters?: {
diff --git a/src/extra_styles.css b/src/extra_styles.css
index 741c49c6f681474c3abe5e514be06dcd32320068..82f71ff959dc619fe28f06c56fab628041b69b03 100644
--- a/src/extra_styles.css
+++ b/src/extra_styles.css
@@ -266,6 +266,11 @@ markdown-dom p
   animation: spinning 700ms linear infinite running;
 }
 
+.spinning
+{
+  animation: spinning 700ms linear infinite running;
+}
+
 .theme-controlled.btn,
 .theme-controlled.btn,
 .theme-controlled.btn
diff --git a/src/ui/topMenu/module.ts b/src/ui/topMenu/module.ts
index 241a0e3d27543f3968c81d4964834b3f7dce2fa8..71c28c73963e064bfbc1d181af1dc0a6b8707627 100644
--- a/src/ui/topMenu/module.ts
+++ b/src/ui/topMenu/module.ts
@@ -15,6 +15,7 @@ import { TopMenuCmp } from "./topMenuCmp/topMenu.components";
 import { UserAnnotationsModule } from "src/atlasComponents/userAnnotations";
 import { QuickTourModule } from "src/ui/quickTour/module";
 import { KeyFrameModule } from "src/keyframesModule/module";
+import { AtlasDownloadModule } from "src/atlas-download/atlas-download.module";
 
 @NgModule({
   imports: [
@@ -33,6 +34,7 @@ import { KeyFrameModule } from "src/keyframesModule/module";
     UserAnnotationsModule,
     KeyFrameModule,
     QuickTourModule,
+    AtlasDownloadModule,
   ],
   declarations: [
     TopMenuCmp
diff --git a/src/ui/topMenu/topMenuCmp/topMenu.components.ts b/src/ui/topMenu/topMenuCmp/topMenu.components.ts
index 19bd0ade69a6850a56712527af4ed665ed5e00d1..1480ecdfc96496ef9965a7132029ac0415abf0cd 100644
--- a/src/ui/topMenu/topMenuCmp/topMenu.components.ts
+++ b/src/ui/topMenu/topMenuCmp/topMenu.components.ts
@@ -5,7 +5,7 @@ import {
   TemplateRef,
   ViewChild,
 } from "@angular/core";
-import { Observable, of } from "rxjs";
+import { Observable } from "rxjs";
 import { map } from "rxjs/operators";
 import { AuthService } from "src/auth";
 import { MatDialog, MatDialogConfig, MatDialogRef } from "@angular/material/dialog";
@@ -53,24 +53,23 @@ export class TopMenuCmp {
 
   public user$: Observable<any>
   public userBtnTooltip$: Observable<string>
-  public favDataEntries$: Observable<Partial<any>[]>
 
   public pluginTooltipText: string = `Plugins and Tools`
   public screenshotTooltipText: string = 'Take screenshot'
   public annotateTooltipText: string = 'Start annotating'
   public keyFrameText = `Start KeyFrames`
+
+  busyTxt = 'Preparing bundle for download ...'
+  idleTxt = 'Download the atlas bundle'
   
   public quickTourData: IQuickTourData = {
     description: QUICKTOUR_DESC.TOP_MENU,
     order: 8,
   }
 
-  public pinnedDsNotAvail = 'We are reworking pinned dataset feature. Please check back later.'
-  @ViewChild('savedDatasets', { read: TemplateRef })
-  private savedDatasetTmpl: TemplateRef<any>
-
-  public openPinnedDatasets(){
-    // this.bottomSheet.open(this.savedDatasetTmpl)
+  public downloadAtlas: IQuickTourData = {
+    description: 'You can download what you see in the viewer with this button.',
+    order: 9
   }
 
   constructor(
@@ -86,8 +85,6 @@ export class TopMenuCmp {
         ? `Logged in as ${(user && user.name) ? user.name : 'Unknown name'}`
         : `Not logged in`),
     )
-
-    this.favDataEntries$ = of([])
   }
 
   private dialogRef: MatDialogRef<any>
diff --git a/src/ui/topMenu/topMenuCmp/topMenu.template.html b/src/ui/topMenu/topMenuCmp/topMenu.template.html
index 42712e8a8894c622ff5b8832b195255ecd877ee2..a33377a72f41a7126bd272754e3862651fd8b86a 100644
--- a/src/ui/topMenu/topMenuCmp/topMenu.template.html
+++ b/src/ui/topMenu/topMenuCmp/topMenu.template.html
@@ -102,22 +102,27 @@
 <!-- pinned dataset btn -->
 <ng-template #pinnedDatasetBtnTmpl>
   <div class="btnWrapper"
-    (click)="openPinnedDatasets()"
-    [matBadge]="(favDataEntries$ | async)?.length > 0 ? (favDataEntries$ | async)?.length : null "
-    matBadgeColor="accent"
-    matBadgePosition="above after"
-    [matBadgeDescription]="PINNED_DATASETS_BADGE_DESC"
-    [matTooltip]="pinnedDsNotAvail"
+    [matTooltip]="(atlasDlDct.busy$| async) ? busyTxt : idleTxt"
+    quick-tour
+    [quick-tour-description]="downloadAtlas.description"
+    [quick-tour-order]="downloadAtlas.order"
+    sxplrAtlasDownload
     aria-disabled="true"
-    role="button">
+    role="button"
+    #atlasDlDct="atlasDlDct">
     <iav-dynamic-mat-button
-      [attr.pinned-datasets-length]="(favDataEntries$ | async)?.length"
       [iav-dynamic-mat-button-style]="matBtnStyle"
       [iav-dynamic-mat-button-color]="matBtnColor"
-      [iav-dynamic-mat-button-disabled]="true"
+      [iav-dynamic-mat-button-disabled]="atlasDlDct.busy$ | async"
       iav-dynamic-mat-button-aria-label="Show pinned datasets">
 
-      <i class="fas fa-thumbtack"></i>
+      <ng-template [ngIf]="atlasDlDct.busy$ | async" [ngIfElse]="dlEnabledTmpl">
+        <i class="spinning fas fa-spinner"></i>
+      </ng-template>
+
+      <ng-template #dlEnabledTmpl>
+        <i class="fas fa-download"></i>
+      </ng-template>
     </iav-dynamic-mat-button>
   </div>
 </ng-template>
@@ -246,37 +251,3 @@
     </config-component>
   </mat-dialog-content>
 </ng-template>
-
-<!-- saved dataset tmpl -->
-
-<ng-template #savedDatasets>
-  <mat-list rol="list"
-    aria-label="Pinned datasets panel">
-    <h3 mat-subheader>
-      <span>
-        Pinned Datasets
-      </span>
-
-      <!-- bulk download btn -->
-    </h3>
-
-    <!-- place holder when no fav data is available -->
-    <mat-card *ngIf="(!(favDataEntries$ | async)) || (favDataEntries$ | async).length === 0">
-      <mat-card-content class="muted">
-        No pinned datasets.
-      </mat-card-content>
-    </mat-card>
-
-    <!-- render all fav dataset as mat list -->
-    <!-- TODO maybe use virtual scroll here? -->
-
-    <mat-list-item
-      class="align-items-center"
-      *ngFor="let ds of (favDataEntries$ | async)"
-      role="listitem">
-
-      <!-- TODO render fav dataset -->
-
-    </mat-list-item>
-  </mat-list>
-</ng-template>
diff --git a/src/util/fn.ts b/src/util/fn.ts
index 713bc7cbe70fff8c149fdc128ada1de3671936cf..ff9b1c07fc1d93938e4f6e62a990aa34ce84422a 100644
--- a/src/util/fn.ts
+++ b/src/util/fn.ts
@@ -431,3 +431,9 @@ export function defaultdict<T>(fn: () => T): Record<string, T> {
     },
   })
 }
+
+export function wait(ms: number){
+  return new Promise(rs => setTimeout(() => {
+    rs(null)
+  }, ms))
+}