diff --git a/src/components/editors/AudioEditor/index.tsx b/src/components/editors/AudioEditor/index.tsx
index a22ea0d1..9826c74c 100644
--- a/src/components/editors/AudioEditor/index.tsx
+++ b/src/components/editors/AudioEditor/index.tsx
@@ -3,8 +3,10 @@ import { INode } from '~/redux/types';
 import { connect } from 'react-redux';
 import { UPLOAD_TYPES } from '~/redux/uploads/constants';
 import { ImageGrid } from '../ImageGrid';
+import { AudioGrid } from '../AudioGrid';
 import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
 import { selectUploads } from '~/redux/uploads/selectors';
+import * as styles from './styles.scss';
 
 const mapStateToProps = selectUploads;
 const mapDispatchToProps = {
@@ -25,23 +27,45 @@ const AudioEditorUnconnected: FC<IProps> = ({ data, setData, temp, statuses }) =
     [data.files]
   );
 
-  const pending_images = useMemo(() => temp.filter(id => !!statuses[id]).map(id => statuses[id]), [
-    temp,
-    statuses,
-  ]);
+  const pending_images = useMemo(
+    () =>
+      temp
+        .filter(id => !!statuses[id] && statuses[id].type === UPLOAD_TYPES.IMAGE)
+        .map(id => statuses[id]),
+    [temp, statuses]
+  );
 
   const audios = useMemo(
     () => data.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO),
     [data.files]
   );
 
+  const pending_audios = useMemo(
+    () =>
+      temp
+        .filter(id => !!statuses[id] && statuses[id].type === UPLOAD_TYPES.AUDIO)
+        .map(id => statuses[id]),
+    [temp, statuses]
+  );
+
   const setImages = useCallback(files => setData({ ...data, files: [...files, ...audios] }), [
     setData,
     data,
     audios,
   ]);
 
-  return <ImageGrid files={images} setFiles={setImages} locked={pending_images} />;
+  const setAudios = useCallback(files => setData({ ...data, files: [...files, ...images] }), [
+    setData,
+    data,
+    images,
+  ]);
+
+  return (
+    <div className={styles.wrap}>
+      <ImageGrid files={images} setFiles={setImages} locked={pending_images} />
+      <AudioGrid files={audios} setFiles={setAudios} locked={pending_audios} />
+    </div>
+  );
 };
 
 const AudioEditor = connect(
diff --git a/src/components/editors/AudioEditor/styles.scss b/src/components/editors/AudioEditor/styles.scss
new file mode 100644
index 00000000..4909ae19
--- /dev/null
+++ b/src/components/editors/AudioEditor/styles.scss
@@ -0,0 +1,4 @@
+.wrap {
+  padding-bottom: 64px;
+  min-height: 200px;
+}
diff --git a/src/components/editors/AudioGrid/index.tsx b/src/components/editors/AudioGrid/index.tsx
new file mode 100644
index 00000000..8dd8556d
--- /dev/null
+++ b/src/components/editors/AudioGrid/index.tsx
@@ -0,0 +1,43 @@
+import React, { FC, useCallback } from 'react';
+import { SortEnd } from 'react-sortable-hoc';
+import * as styles from './styles.scss';
+import { IFile } from '~/redux/types';
+import { IUploadStatus } from '~/redux/uploads/reducer';
+import { moveArrItem } from '~/utils/fn';
+import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid';
+
+interface IProps {
+  files: IFile[];
+  setFiles: (val: IFile[]) => void;
+  locked: IUploadStatus[];
+}
+
+const AudioGrid: FC<IProps> = ({ files, setFiles, locked }) => {
+  const onMove = useCallback(
+    ({ oldIndex, newIndex }: SortEnd) => {
+      setFiles(moveArrItem(oldIndex, newIndex, files.filter(file => !!file)) as IFile[]);
+    },
+    [setFiles, files]
+  );
+
+  const onDrop = useCallback(
+    (remove_id: IFile['id']) => {
+      setFiles(files.filter(file => file && file.id !== remove_id));
+    },
+    [setFiles, files]
+  );
+
+  return (
+    <SortableAudioGrid
+      onDrop={onDrop}
+      onSortEnd={onMove}
+      axis="xy"
+      items={files}
+      locked={locked}
+      pressDelay={window.innerWidth < 768 ? 200 : 0}
+      helperClass={styles.helper}
+    />
+  );
+};
+
+export { AudioGrid };
diff --git a/src/components/editors/AudioGrid/styles.scss b/src/components/editors/AudioGrid/styles.scss
new file mode 100644
index 00000000..7ab40804
--- /dev/null
+++ b/src/components/editors/AudioGrid/styles.scss
@@ -0,0 +1,4 @@
+.helper {
+  opacity: 0.5;
+  z-index: 10 !important;
+}
diff --git a/src/components/editors/SortableAudioGrid/index.tsx b/src/components/editors/SortableAudioGrid/index.tsx
new file mode 100644
index 00000000..5aa4c293
--- /dev/null
+++ b/src/components/editors/SortableAudioGrid/index.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { SortableContainer } from 'react-sortable-hoc';
+import { AudioUpload } from '~/components/upload/AudioUpload';
+import * as styles from './styles.scss';
+import { SortableImageGridItem } from '~/components/editors/SortableImageGridItem';
+import { IFile } from '~/redux/types';
+import { IUploadStatus } from '~/redux/uploads/reducer';
+import { AudioPlayer } from '~/components/media/AudioPlayer';
+
+const SortableAudioGrid = SortableContainer(
+  ({
+    items,
+    locked,
+    onDrop,
+  }: {
+    items: IFile[];
+    locked: IUploadStatus[];
+    onDrop: (file_id: IFile['id']) => void;
+  }) => (
+    <div className={styles.grid}>
+      {items
+        .filter(file => file && file.id)
+        .map((file, index) => (
+          <SortableImageGridItem key={file.id} index={index} collection={0}>
+            <AudioPlayer file={file} onDrop={onDrop} />
+          </SortableImageGridItem>
+        ))}
+
+      {locked.map((item, index) => (
+        <SortableImageGridItem key={item.temp_id} index={index} collection={1} disabled>
+          <AudioUpload title={item.name} progress={item.progress} is_uploading />
+        </SortableImageGridItem>
+      ))}
+    </div>
+  )
+);
+
+export { SortableAudioGrid };
diff --git a/src/components/editors/SortableAudioGrid/styles.scss b/src/components/editors/SortableAudioGrid/styles.scss
new file mode 100644
index 00000000..7dc895db
--- /dev/null
+++ b/src/components/editors/SortableAudioGrid/styles.scss
@@ -0,0 +1,13 @@
+.grid {
+  box-sizing: border-box;
+
+  display: grid;
+  grid-column-gap: $gap;
+  grid-row-gap: $gap;
+  grid-template-columns: auto;
+  grid-template-rows: $comment_height;
+
+  @media (max-width: 600px) {
+    grid-template-columns: repeat(auto-fill, minmax(30vw, 1fr));
+  }
+}
diff --git a/src/components/editors/SortableAudioGridItem/index.tsx b/src/components/editors/SortableAudioGridItem/index.tsx
new file mode 100644
index 00000000..2b7ff3d0
--- /dev/null
+++ b/src/components/editors/SortableAudioGridItem/index.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import { SortableElement } from 'react-sortable-hoc';
+
+import * as styles from './styles.scss';
+
+const SortableAudioGridItem = SortableElement(({ children }) => (
+  <div className={styles.item}>{children}</div>
+));
+
+export { SortableAudioGridItem };
diff --git a/src/components/editors/SortableAudioGridItem/styles.scss b/src/components/editors/SortableAudioGridItem/styles.scss
new file mode 100644
index 00000000..81fd01cb
--- /dev/null
+++ b/src/components/editors/SortableAudioGridItem/styles.scss
@@ -0,0 +1,4 @@
+.item {
+  z-index: 1;
+  box-sizing: border-box;
+}
diff --git a/src/components/editors/SortableImageGrid/styles.scss b/src/components/editors/SortableImageGrid/styles.scss
index c525238b..cbd34386 100644
--- a/src/components/editors/SortableImageGrid/styles.scss
+++ b/src/components/editors/SortableImageGrid/styles.scss
@@ -1,6 +1,4 @@
 .grid {
-  min-height: 200px;
-  padding-bottom: 62px;
   box-sizing: border-box;
 
   display: grid;
diff --git a/src/components/upload/AudioUpload/index.tsx b/src/components/upload/AudioUpload/index.tsx
new file mode 100644
index 00000000..fd150593
--- /dev/null
+++ b/src/components/upload/AudioUpload/index.tsx
@@ -0,0 +1,43 @@
+import React, { FC, useCallback } from 'react';
+import classNames from 'classnames';
+import * as styles from './styles.scss';
+import { ArcProgress } from '~/components/input/ArcProgress';
+import { IFile } from '~/redux/types';
+import { Icon } from '~/components/input/Icon';
+
+interface IProps {
+  id?: IFile['id'];
+  title?: string;
+  progress?: number;
+  onDrop?: (file_id: IFile['id']) => void;
+
+  is_uploading?: boolean;
+}
+
+const AudioUpload: FC<IProps> = ({ title, progress, is_uploading, id, onDrop }) => {
+  const onDropFile = useCallback(() => {
+    if (!id || !onDrop) return;
+    onDrop(id);
+  }, [id, onDrop]);
+
+  return (
+    <div className={styles.wrap}>
+      {id && onDrop && (
+        <div className={styles.drop} onMouseDown={onDropFile}>
+          <Icon icon="close" />
+        </div>
+      )}
+
+      <div className={classNames(styles.thumb_wrap, { is_uploading })}>
+        {is_uploading && (
+          <div className={styles.progress}>
+            <ArcProgress size={40} progress={progress} />
+          </div>
+        )}
+        {title && <div className={styles.title}>{title}</div>}
+      </div>
+    </div>
+  );
+};
+
+export { AudioUpload };
diff --git a/src/components/upload/AudioUpload/styles.scss b/src/components/upload/AudioUpload/styles.scss
new file mode 100644
index 00000000..17882875
--- /dev/null
+++ b/src/components/upload/AudioUpload/styles.scss
@@ -0,0 +1,75 @@
+.wrap {
+  background: lighten($content_bg, 4%);
+  // padding-bottom: 100%;
+  border-radius: $radius;
+  position: relative;
+  user-select: none;
+  height: $comment_height;
+}
+
+.thumb_wrap {
+  // position: absolute;
+  // width: 100%;
+  // height: 100%;
+  z-index: 1;
+  border-radius: $radius;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: row;
+  height: 100%;
+}
+
+.title {
+  flex: 1;
+  border-radius: $radius;
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  box-sizing: border-box;
+}
+
+.progress {
+  flex: 0 0 $comment_height;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  svg {
+    width: 40px;
+    height: 40px;
+    fill: none;
+    fill: white;
+  }
+}
+
+.helper {
+  opacity: 0.3;
+}
+
+.drop {
+  width: 24px;
+  height: 24px;
+  background: #222222;
+  position: absolute;
+  right: $gap;
+  top: $gap;
+  border-radius: 12px;
+  z-index: 2;
+  transition: background-color 250ms, opacity 0.25s;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  svg {
+    width: 20px;
+    height: 20px;
+  }
+
+  &:hover {
+    opacity: 1;
+    background-color: $red;
+  }
+}
diff --git a/src/constants/errors.ts b/src/constants/errors.ts
index 70a8ee47..92aea57b 100644
--- a/src/constants/errors.ts
+++ b/src/constants/errors.ts
@@ -7,6 +7,7 @@ export const ERRORS = {
   TEXT_REQUIRED: 'Text_Required',
   UNKNOWN_NODE_TYPE: 'Unknown_Node_Type',
   URL_INVALID: 'Url_Invalid',
+  FILES_AUDIO_REQUIRED: 'Files_Audio_Required',
 };
 
 export const ERROR_LITERAL = {
@@ -18,4 +19,5 @@ export const ERROR_LITERAL = {
   [ERRORS.TEXT_REQUIRED]: 'Нужно немного текста',
   [ERRORS.UNKNOWN_NODE_TYPE]: 'Неизвестный тип поста',
   [ERRORS.URL_INVALID]: 'Неизвестный адрес',
+  [ERRORS.FILES_AUDIO_REQUIRED]: 'Нужна хотя бы одна песня',
 };
diff --git a/src/redux/uploads/constants.ts b/src/redux/uploads/constants.ts
index 74a1528b..21a03c8a 100644
--- a/src/redux/uploads/constants.ts
+++ b/src/redux/uploads/constants.ts
@@ -38,6 +38,7 @@ export const EMPTY_UPLOAD_STATUS: IUploadStatus = {
   thumbnail_url: null,
   type: null,
   temp_id: null,
+  name: null,
 };
 
 // for targeted cancellation
diff --git a/src/redux/uploads/reducer.ts b/src/redux/uploads/reducer.ts
index 98915b38..38a5d310 100644
--- a/src/redux/uploads/reducer.ts
+++ b/src/redux/uploads/reducer.ts
@@ -12,6 +12,7 @@ export interface IUploadStatus {
   thumbnail_url: string;
   progress: number;
   temp_id: UUID;
+  name: string;
 }
 
 export interface IUploadState {
diff --git a/src/redux/uploads/sagas.ts b/src/redux/uploads/sagas.ts
index 357852e8..94868079 100644
--- a/src/redux/uploads/sagas.ts
+++ b/src/redux/uploads/sagas.ts
@@ -78,8 +78,10 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) {
       {
         preview,
         is_uploading: true,
-        type: file.type,
+        // type: file.type,
         temp_id,
+        type,
+        name: file.name,
       }
     )
   );
@@ -106,7 +108,7 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) {
 
   if (error) {
     return yield put(
-      uploadSetStatus(temp_id, { is_uploading: false, error: data.detail || error })
+      uploadSetStatus(temp_id, { is_uploading: false, error: data.detail || error, type })
     );
   }
 
@@ -116,8 +118,10 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) {
       error: null,
       uuid: data.id,
       url: data.full_path,
+      type,
       thumbnail_url: data.full_path,
       progress: 1,
+      name: file.name,
     })
   );