diff --git a/openbis_ng_ui/src/js/components/common/grid/Grid.jsx b/openbis_ng_ui/src/js/components/common/grid/Grid.jsx
index 6c0f66ee7689524156595ddd7e752d906627af36..32299cf92ffabdb93602e0d674177f4ac62b405f 100644
--- a/openbis_ng_ui/src/js/components/common/grid/Grid.jsx
+++ b/openbis_ng_ui/src/js/components/common/grid/Grid.jsx
@@ -122,8 +122,7 @@ class Grid extends React.PureComponent {
       filterMode,
       filters,
       globalFilter,
-      sort,
-      sortDirection,
+      sortings,
       page,
       pageSize,
       columnsVisibility,
@@ -168,8 +167,7 @@ class Grid extends React.PureComponent {
                     <GridHeaders
                       columns={visibleColumns}
                       rows={rows}
-                      sort={sort}
-                      sortDirection={sortDirection}
+                      sortings={sortings}
                       onSortChange={this.controller.handleSortChange}
                       onMultiselectAllRowsChange={
                         this.controller.handleMultiselectAllRowsChange
diff --git a/openbis_ng_ui/src/js/components/common/grid/GridController.js b/openbis_ng_ui/src/js/components/common/grid/GridController.js
index e4d4bcda8349a27e1616826b686be37df394c54f..1dce97aa1e5fe40c478141341e355db87732aee9 100644
--- a/openbis_ng_ui/src/js/components/common/grid/GridController.js
+++ b/openbis_ng_ui/src/js/components/common/grid/GridController.js
@@ -27,6 +27,17 @@ export default class GridController {
       }
     }
 
+    let sortings = []
+
+    if (props.sort) {
+      sortings.push({
+        columnName: props.sort,
+        sortDirection: props.sortDirection
+          ? props.sortDirection
+          : GridSortingOptions.ASC
+      })
+    }
+
     context.initState({
       loaded: false,
       loading: false,
@@ -48,10 +59,7 @@ export default class GridController {
       allRows: [],
       selectedRow: null,
       multiselectedRows: {},
-      sort: props.sort,
-      sortDirection: props.sortDirection
-        ? props.sortDirection
-        : GridSortingOptions.ASC,
+      sortings: sortings,
       totalCount: 0,
       exportOptions: {
         columns: GridExportOptions.VISIBLE_COLUMNS,
@@ -118,8 +126,7 @@ export default class GridController {
         globalFilter: newState.globalFilter,
         page: newState.page,
         pageSize: newState.pageSize,
-        sort: newState.sort,
-        sortDirection: newState.sortDirection
+        sortings: newState.sortings
       })
       if (_.isArray(loadedResult)) {
         result.rows = loadedResult
@@ -158,8 +165,7 @@ export default class GridController {
       newState.sortedRows = this._sortRows(
         newState.filteredRows,
         newState.allColumns,
-        newState.sort,
-        newState.sortDirection
+        newState.sortings
       )
       newState.totalCount = newState.filteredRows.length
 
@@ -294,25 +300,27 @@ export default class GridController {
   }
 
   _loadColumn(column) {
+    const defaultMatches = function (value, filter) {
+      if (filter) {
+        return value !== null && value !== undefined
+          ? String(value)
+              .trim()
+              .toUpperCase()
+              .includes(filter.trim().toUpperCase())
+          : false
+      } else {
+        return true
+      }
+    }
+
+    const defaultCompare = compare
+
     return {
       ...column,
       name: column.name,
       label: column.label,
       getValue: column.getValue,
       matches: (row, filter) => {
-        function defaultMatches(value, filter) {
-          if (filter) {
-            return value !== null && value !== undefined
-              ? String(value)
-                  .trim()
-                  .toUpperCase()
-                  .includes(filter.trim().toUpperCase())
-              : false
-          } else {
-            return true
-          }
-        }
-
         const value = column.getValue({ row, column, operation: 'match' })
 
         if (column.matchesValue) {
@@ -327,8 +335,7 @@ export default class GridController {
           return defaultMatches(value, filter)
         }
       },
-      compare: (row1, row2) => {
-        const defaultCompare = compare
+      compare: (row1, row2, sortDirection) => {
         const value1 = column.getValue({
           row: row1,
           column,
@@ -339,7 +346,6 @@ export default class GridController {
           column,
           operation: 'compare'
         })
-        const { sortDirection } = this.context.getState()
 
         if (column.compareValue) {
           return column.compareValue({
@@ -550,13 +556,32 @@ export default class GridController {
     }
   }
 
-  _sortRows(rows, columns, sort, sortDirection) {
-    if (sort) {
-      const column = _.find(columns, ['name', sort])
-      if (column) {
+  _sortRows(rows, columns, sortings) {
+    if (sortings && sortings.length > 0) {
+      const columnSortings = []
+
+      sortings.forEach(sorting => {
+        const column = _.find(columns, ['name', sorting.columnName])
+        if (column) {
+          columnSortings.push({
+            column,
+            sorting
+          })
+        }
+      })
+
+      if (columnSortings.length > 0) {
         return rows.sort((t1, t2) => {
-          let sign = sortDirection === GridSortingOptions.ASC ? 1 : -1
-          return sign * column.compare(t1, t2)
+          let result = 0
+          let index = 0
+          while (index < columnSortings.length && result === 0) {
+            const { column, sorting } = columnSortings[index]
+            const sign =
+              sorting.sortDirection === GridSortingOptions.ASC ? 1 : -1
+            result = sign * column.compare(t1, t2, sorting.sortDirection)
+            index++
+          }
+          return result
         })
       }
     }
@@ -811,25 +836,58 @@ export default class GridController {
     await this._saveSettings()
   }
 
-  async handleSortChange(column) {
+  async handleSortChange(column, append) {
     if (!column.sortable) {
       return
     }
 
+    function createInitialSorting(column) {
+      return {
+        columnName: column.name,
+        sortDirection: GridSortingOptions.ASC
+      }
+    }
+
+    function createReversedSorting(column, sorting) {
+      return {
+        columnName: column.name,
+        sortDirection:
+          sorting.sortDirection === GridSortingOptions.ASC
+            ? GridSortingOptions.DESC
+            : GridSortingOptions.ASC
+      }
+    }
+
     await this.context.setState(state => {
-      if (column.name === state.sort) {
-        return {
-          sortDirection:
-            state.sortDirection === GridSortingOptions.ASC
-              ? GridSortingOptions.DESC
-              : GridSortingOptions.ASC
+      const newSortings = []
+
+      const index = _.findIndex(
+        state.sortings,
+        sorting => sorting.columnName === column.name
+      )
+      const sorting = state.sortings[index]
+
+      if (append) {
+        if (index !== -1) {
+          newSortings.push(...state.sortings)
+          newSortings.splice(index, 1)
+        } else {
+          newSortings.push(...state.sortings)
+          newSortings.push(createInitialSorting(column))
         }
       } else {
-        return {
-          sort: column.name,
-          sortDirection: GridSortingOptions.ASC
+        if (index !== -1) {
+          newSortings.push(...state.sortings)
+          newSortings[index] = createReversedSorting(column, sorting)
+        } else {
+          newSortings.push(createInitialSorting(column))
         }
       }
+
+      return {
+        page: 0,
+        sortings: newSortings
+      }
     })
 
     await this.load()
@@ -1006,8 +1064,7 @@ export default class GridController {
           globalFilter: state.globalFilter,
           page: 0,
           pageSize: 1000000,
-          sort: state.sort,
-          sortDirection: state.sortDirection
+          sortings: state.sortings
         })
         data = loadedResult.rows
       }
@@ -1064,14 +1121,9 @@ export default class GridController {
     return pageSize
   }
 
-  getSort() {
-    const { sort } = this.context.getState()
-    return sort
-  }
-
-  getSortDirection() {
-    const { sortDirection } = this.context.getState()
-    return sortDirection
+  getSortings() {
+    const { sortings } = this.context.getState()
+    return sortings
   }
 
   getFilters() {
diff --git a/openbis_ng_ui/src/js/components/common/grid/GridHeader.jsx b/openbis_ng_ui/src/js/components/common/grid/GridHeader.jsx
index 72fec644bc579b84d0811a36416979d76cab7586..cac5f561e0b6c211541c05679521fa4b51601648 100644
--- a/openbis_ng_ui/src/js/components/common/grid/GridHeader.jsx
+++ b/openbis_ng_ui/src/js/components/common/grid/GridHeader.jsx
@@ -15,6 +15,13 @@ const styles = theme => ({
     '&:last-child': {
       paddingRight: theme.spacing(2)
     }
+  },
+  sortIndex: {
+    color: theme.typography.label.color,
+    position: 'absolute',
+    right: 0,
+    paddingTop: '10px',
+    fontSize: theme.typography.label.fontSize
   }
 })
 
@@ -24,20 +31,20 @@ class GridHeader extends React.PureComponent {
     this.handleClick = this.handleClick.bind(this)
   }
 
-  handleClick() {
+  handleClick(event) {
     const { column, onSortChange } = this.props
     if (onSortChange) {
-      onSortChange(column)
+      onSortChange(column, event.ctrlKey || event.metaKey)
     }
   }
 
   render() {
     logger.log(logger.DEBUG, 'GridHeader.render')
 
-    const { column, sort, sortDirection, classes } = this.props
+    const { column, sortCount, sortIndex, sortDirection, classes } = this.props
 
     if (column.sortable) {
-      const active = sort === column.name
+      const active = sortIndex !== null && sortDirection !== null
       return (
         <TableCell classes={{ root: classes.cell }}>
           <TableSortLabel
@@ -46,6 +53,9 @@ class GridHeader extends React.PureComponent {
             onClick={this.handleClick}
           >
             {column.label}
+            {sortCount > 1 && sortIndex !== null && (
+              <span className={classes.sortIndex}>{sortIndex + 1}</span>
+            )}
           </TableSortLabel>
         </TableCell>
       )
diff --git a/openbis_ng_ui/src/js/components/common/grid/GridHeaders.jsx b/openbis_ng_ui/src/js/components/common/grid/GridHeaders.jsx
index a9482cf541edb9a56e3c3c2a3e46af7c579f53cc..fef28aebf81950d34ff278f6cf76fe077685ab6b 100644
--- a/openbis_ng_ui/src/js/components/common/grid/GridHeaders.jsx
+++ b/openbis_ng_ui/src/js/components/common/grid/GridHeaders.jsx
@@ -51,14 +51,20 @@ class GridHeaders extends React.PureComponent {
   }
 
   renderHeaderCell(column) {
-    const { sort, sortDirection, onSortChange } = this.props
+    const { sortings, onSortChange } = this.props
+
+    const index = _.findIndex(
+      sortings,
+      sorting => sorting.columnName === column.name
+    )
 
     return (
       <GridHeader
         key={column.name}
         column={column}
-        sort={sort}
-        sortDirection={sortDirection}
+        sortCount={sortings.length}
+        sortIndex={index !== -1 ? index : null}
+        sortDirection={index !== -1 ? sortings[index].sortDirection : null}
         onSortChange={onSortChange}
       />
     )
diff --git a/openbis_ng_ui/src/js/components/tools/common/HistoryGrid.jsx b/openbis_ng_ui/src/js/components/tools/common/HistoryGrid.jsx
index 5d7e7e6bb8be753ce86218e0ab7088fef2f9d19b..4fac0c10d7803148123bfb9f2eca22b439247027 100644
--- a/openbis_ng_ui/src/js/components/tools/common/HistoryGrid.jsx
+++ b/openbis_ng_ui/src/js/components/tools/common/HistoryGrid.jsx
@@ -31,10 +31,7 @@ class HistoryGrid extends React.PureComponent {
     }
   }
 
-  async loadHistory(
-    eventType,
-    { filters, page, pageSize, sort, sortDirection }
-  ) {
+  async loadHistory(eventType, { filters, page, pageSize, sortings }) {
     const criteria = new openbis.EventSearchCriteria()
     criteria.withEventType().thatEquals(eventType)
 
@@ -68,9 +65,9 @@ class HistoryGrid extends React.PureComponent {
     fo.from(page * pageSize)
     fo.count(pageSize)
 
-    if (sort && sortDirection) {
-      fo.sortBy()[sort]()[sortDirection]()
-    }
+    sortings.forEach(sorting => {
+      fo.sortBy()[sorting.columnName]()[sorting.sortDirection]()
+    })
 
     const result = await openbis.searchEvents(criteria, fo)
 
@@ -218,7 +215,6 @@ class HistoryGrid extends React.PureComponent {
             name: 'registrationDate',
             label: messages.get(messages.DATE),
             sortable: true,
-            sort: 'desc',
             getValue: ({ row }) => date.format(row.registrationDate.value),
             renderFilter: ({ value, onChange }) => {
               return <DateRangeField value={value} onChange={onChange} />
@@ -226,6 +222,8 @@ class HistoryGrid extends React.PureComponent {
           }
         ]}
         loadRows={this.load}
+        sort='registrationDate'
+        sortDirection='desc'
         selectable={true}
       />
     )
diff --git a/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridFilterWrapper.js b/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridFilterWrapper.js
index 3ff0b455c1ed421e08389464f24b834c4530e047..aaf6946a605227e93ea7503285fef0035fea29c5 100644
--- a/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridFilterWrapper.js
+++ b/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridFilterWrapper.js
@@ -1,6 +1,6 @@
 import BaseWrapper from '@srcTest/js/components/common/wrapper/BaseWrapper.js'
 
-export default class GridColumnFilterWrapper extends BaseWrapper {
+export default class GridFilterWrapper extends BaseWrapper {
   getValue() {
     return this.getStringValue(this.wrapper.prop('filter'))
   }
diff --git a/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridHeaderWrapper.js b/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridHeaderWrapper.js
index 1b67986a30fbd7c8a59656a1e8abab50643a7276..863e5ac0bb63f25a31383ba5a021e610ff1ca641 100644
--- a/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridHeaderWrapper.js
+++ b/openbis_ng_ui/srcTest/js/components/common/grid/wrapper/GridHeaderWrapper.js
@@ -1,12 +1,12 @@
 import BaseWrapper from '@srcTest/js/components/common/wrapper/BaseWrapper.js'
 
-export default class GridColumnLabelWrapper extends BaseWrapper {
+export default class GridHeaderWrapper extends BaseWrapper {
   getValue() {
     return this.wrapper.text()
   }
 
   click() {
-    this.wrapper.instance().handleClick()
+    this.wrapper.instance().handleClick({})
   }
 
   toJSON() {