diff --git a/gui/src/components/editQuantity/NumberEditQuantity.js b/gui/src/components/editQuantity/NumberEditQuantity.js
index 5d9627766de2557770e30950b8427ea86c8b2513..578d1ef2d418a2cd0a93a16f89c5d96b94ba7f4e 100644
--- a/gui/src/components/editQuantity/NumberEditQuantity.js
+++ b/gui/src/components/editQuantity/NumberEditQuantity.js
@@ -19,7 +19,8 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
 import {TextField, makeStyles, Box, Checkbox, Tooltip} from '@material-ui/core'
 import Autocomplete from '@material-ui/lab/Autocomplete'
 import PropTypes from 'prop-types'
-import {Quantity, parseQuantity} from '../units/Quantity'
+import {Quantity} from '../units/Quantity'
+import {parse} from '../units/common'
 import {Unit} from '../units/Unit'
 import {getUnits} from '../units/UnitContext'
 import {debounce, isNil} from 'lodash'
@@ -92,7 +93,7 @@ export const NumberField = React.memo((props) => {
     }
 
     // Try to parse the quantity. Value is required, unit is optional.
-    const {unit: parsedUnit, value, valueString, error} = parseQuantity(input, dimension, true, false)
+    const {unit: parsedUnit, value, valueString, error} = parse(input, {dimension, requireValue: true})
     previousNumberPart.current = valueString
     if (parsedUnit) {
       previousUnitLabel.current = parsedUnit.label()
@@ -306,7 +307,7 @@ export const UnitSelect = React.memo(({options, unit, onChange, dimension, disab
 
   // Validate input and submit unit if valid
   const submit = useCallback((val) => {
-    const {unit, error} = parseQuantity(val, dimension, false, true)
+    const {unit, error} = parse(val, {dimension, requireUnit: true})
     if (error) {
       setError(error)
     } else {
diff --git a/gui/src/components/search/input/InputRange.js b/gui/src/components/search/input/InputRange.js
index 01b6a37c04f868b20a8e028516ffb7399fe8507d..17c04950b0fafe6717ccadd86ec643c6f50ca87b 100644
--- a/gui/src/components/search/input/InputRange.js
+++ b/gui/src/components/search/input/InputRange.js
@@ -222,7 +222,7 @@ export const Range = React.memo(({
       return undefined
     }
     const rangeSI = maxLocal - minLocal
-    const range = new Quantity(rangeSI, unitStorage).to(xAxis.unit).value()
+    const range = new Quantity(rangeSI, unitStorage.toDelta()).to(xAxis.unit).value()
     const intervalCustom = getInterval(range, nSteps, xAxis.dtype)
     return new Quantity(intervalCustom, xAxis.unit).to(unitStorage).value()
   }, [maxLocal, minLocal, discretization, nSteps, xAxis.dtype, xAxis.unit, unitStorage])
diff --git a/gui/src/components/units/Quantity.js b/gui/src/components/units/Quantity.js
index 4926ffac61d2db5651f833bf38f5d3cb7fabb6ed..63c66a31918617b8b14d4083983523436a13aa89 100644
--- a/gui/src/components/units/Quantity.js
+++ b/gui/src/components/units/Quantity.js
@@ -17,8 +17,7 @@
  */
 
 import {isNumber, isArray} from 'lodash'
-import {Unit, normalizeExpression} from './Unit'
-import { Unit as UnitMathJS } from 'mathjs'
+import {Unit} from './Unit'
 import {mapDeep} from '../../utils'
 
 /**
@@ -36,14 +35,14 @@ export class Quantity {
   constructor(value, unit, normalized = false) {
     this.unit = new Unit(unit)
     if (!isNumber(value) && !isArray(value)) {
-      throw Error('Please provide the value as a number, or as a multidimensional array of numbers.')
+      throw Error('Please provide the value for a Quantity as a number, or as a multidimensional array of numbers.')
     }
 
     // This attribute stores the quantity value in 'normalized' form that is
     // given in the base units (=SI). This value should only be determined once
-    // during the unit initialization and all calls to value() will then lazily
-    // determine the value in the currently set units. This avoids 'drift' in
-    // the value caused by several consecutive changes of the units.
+    // during initialization and all calls to value() will then lazily determine
+    // the value in the currently set units. This avoids 'drift' in the value
+    // caused by several consecutive changes of the units.
     this.normalized_value = normalized ? value : this.normalize(value)
   }
 
@@ -60,8 +59,29 @@ export class Quantity {
    * @param {n-dimensional array} value Value in currently set units.
    * @returns Value in base units.
    */
-  normalize(value) {
-    return mapDeep(value, (x) => this.unit.mathjsUnit._normalize(x))
+  normalize(values) {
+    // Pre-calculate coefficients for currently set units. This speeds up
+    // conversion for large arrays.
+    const unit = this.unit.mathjsUnit
+    const ignoreOffset = unit._isDerived()
+    const coefficients = this.conversion_coefficients()
+
+    return mapDeep(values, (value) => {
+      if (value === null || value === undefined) {
+        return value
+      }
+
+      let result = value
+      for (let i = 0; i < unit.units.length; i++) {
+        const unitDef = unit.units[i]
+        const unitOffset = unitDef.unit.offset
+        const variable = (ignoreOffset || unitDef.delta)
+          ? result
+          : result + unitOffset
+        result = variable * coefficients[i]
+      }
+      return result
+    })
   }
 
   /**
@@ -69,8 +89,45 @@ export class Quantity {
    * @param {n-dimensional array} value Value in base units.
    * @returns Value in currently set units.
    */
-  denormalize(value) {
-    return mapDeep(value, (x) => this.unit.mathjsUnit._denormalize(x))
+  denormalize(values) {
+    // Pre-calculate coefficients for currently set units. This speeds up
+    // conversion for large arrays.
+    const unit = this.unit.mathjsUnit
+    const ignoreOffset = unit._isDerived()
+    const coefficients = this.conversion_coefficients()
+
+    return mapDeep(values, (value) => {
+      if (value === null || value === undefined) {
+        return value
+      }
+
+      let result = value
+      for (let i = 0; i < unit.units.length; i++) {
+        const unitDef = unit.units[i]
+        const unitOffset = unitDef.unit.offset
+        result = (ignoreOffset || unitDef.delta)
+          ? result / coefficients[i]
+          : result / coefficients[i] - unitOffset
+      }
+      return result
+    })
+  }
+
+  /**
+   * Returns a set of conversion coefficients based on the currently set units.
+   * @returns Array of conversion coefficients, one for each unit that is present.
+   */
+  conversion_coefficients() {
+    const unit = this.unit.mathjsUnit
+    const coefficients = []
+    for (let i = 0; i < unit.units.length; i++) {
+      const unitDef = unit.units[i]
+      const unitValue = unitDef.unit.value
+      const unitPrefixValue = unitDef.prefix.value
+      const unitPower = unitDef.power
+      coefficients.push((Math.pow(unitValue * unitPrefixValue, unitPower)))
+    }
+    return coefficients
   }
 
   label() {
@@ -106,83 +163,3 @@ export class Quantity {
     }
   }
 }
-
-/**
- * Convenience function for parsing value and unit information from a string.
- *
- * @param {string} input The input string to parse
- * @param {string} dimension Dimension for the unit. Note that you should use
- *   the base dimensions which you can get e.g. with .dimension(true). Defaults
- *   to 'dimensionless' if not specified. If you want to disable dimension
- *   checks, use null.
- * @param {boolean} requireValue Whether an explicit numeric value is required at the start of the input.
- * @param {boolean} requireUnit Whether an explicit unit in the input is required at the end of the input.
- * @returns Object containing the following properties, if available:
- *  - value: Numerical value as a number
- *  - valueString: The original number input as a string. Note that this can only return
- *    the number when it is used as a prefix, and does not work with numbers that are
- *    part of a complex expression, e.g. 300 eV / 1000 K.
- *  - unit: Unit instance
- *  - error: Error messsage
- */
-export function parseQuantity(input, dimension = 'dimensionless', requireValue = false, requireUnit = false) {
-  input = input.trim()
-  let error
-  let value
-  let valueString = input.match(/^[+-]?((\d+\.\d+|\d+\.|\.\d?|\d+)(e|e\+|e-)\d+|(\d+\.\d+|\d+\.|\.\d?|\d+))?/)?.[0]
-  const unitString = input.substring(valueString.length)?.trim() || ''
-
-  // Check value if required
-  if (valueString === '') {
-    valueString = undefined
-    value = undefined
-    if (requireValue) {
-       error = 'Enter a valid numerical value.'
-    }
-  } else {
-    value = Number(valueString)
-  }
-
-  // Check unit if required
-  if (requireUnit) {
-    if (unitString === '') {
-      return {valueString, value, error: 'Unit is required.'}
-    }
-  }
-
-  // Try to parse with MathJS: it can extract the unit even when it is mixed
-  // with numbers
-  input = normalizeExpression(input)
-  let unitMathJS
-  try {
-    unitMathJS = UnitMathJS.parse(input, {allowNoUnits: true})
-  } catch (e) {
-    return {valueString, error: e.message}
-  }
-
-  let unit
-  unitMathJS.value = null
-  try {
-    unit = new Unit(unitMathJS)
-  } catch (e) {
-    error = e.msg
-  }
-  if (error) {
-    return {valueString, value, unit, error}
-  }
-
-  // If unit is not required and it is dimensionless, return without new unit
-  if (!requireUnit && unit.dimension() === 'dimensionless') {
-    return {valueString, value}
-  }
-
-  // TODO: This check is not enough: the input may be compatible after the base
-  // units are compared.
-  if (dimension !== null) {
-    if (!(unit.dimension(true) === dimension || unit.dimension(false) === dimension)) {
-      error = `Unit "${unit.label(false)}" is incompatible with dimension "${dimension}".`
-    }
-  }
-
-  return {value, valueString, unit, error}
-}
diff --git a/gui/src/components/units/Quantity.spec.js b/gui/src/components/units/Quantity.spec.js
index 8179c40a9f212118887ede8c054a7e6c384b3b1a..cd278f551c83904cef2d91b0399bfe5062d9105d 100644
--- a/gui/src/components/units/Quantity.spec.js
+++ b/gui/src/components/units/Quantity.spec.js
@@ -16,8 +16,7 @@
  * limitations under the License.
  */
 
-import { Unit } from './Unit'
-import { Quantity, parseQuantity } from './Quantity'
+import { Quantity } from './Quantity'
 import { dimensionMap } from './UnitContext'
 
 test('conversion works both ways for each compatible unit', async () => {
@@ -48,11 +47,13 @@ test.each([
   ['division', 'm/s', 'angstrom/femtosecond', 1, 0.00001],
   ['multiplication', 'm*s', 'angstrom*femtosecond', 1, 9.999999999999999e+24],
   ['power with hat', 'm^2', 'angstrom^2', 1, 99999999999999980000],
+  ['power with unit that has offset', 'celsius**2', 'fahrenheit**2', 3, 9.72], // The units here automatically become delta units due to multiplication.
   ['power with double asterisk (single)', 'm**2', 'angstrom**2', 1, 99999999999999980000],
   ['power with double asterisk (multiple)', 'm**2 / s**2', 'angstrom**2 / ms**2', 1, 99999999999999.98],
-  ['explicit delta (single)', 'delta_celsius', 'delta_K', 1, 274.15],
+  ['explicit delta identity (single)', 'delta_celsius', 'delta_celsius', 1, 1],
+  ['explicit delta (single)', 'delta_celsius', 'delta_K', 1, 1],
   ['explicit delta (multiple)', 'delta_celsius / delta_celsius', 'delta_K / delta_K', 1, 1],
-  ['explicit delta symbol (single)', 'Δcelsius', 'ΔK', 1, 274.15],
+  ['explicit delta symbol (single)', 'Δcelsius', 'ΔK', 1, 1],
   ['explicit delta symbol (multiple)', 'Δcelsius / Δcelsius', 'ΔK / ΔK', 1, 1],
   ['combined', 'm*m/s^2', 'angstrom^2/femtosecond^2', 1, 9.999999999999999e-11],
   ['negative exponent', 's^-2', 'femtosecond^-2', 1, 1e-30],
@@ -76,7 +77,10 @@ test.each([
   ['combination', 'a_u_force * angstrom', {force: {definition: 'newton'}, length: {definition: 'meter'}}, 1, 8.23872349823899e-18],
   ['use base units if derived unit not defined in system', 'newton * meter', {mass: {definition: 'kilogram'}, time: {definition: 'second'}, length: {definition: 'meter'}}, 1, 1],
   ['unit definition with prefix', 'kg^2', {mass: {definition: 'mg'}}, 1, 1e12],
-  ['expression as definition', 'N', {force: {definition: '(kg m) / s^2'}}, 1, 1]
+  ['expression as definition', 'N', {force: {definition: '(kg m) / s^2'}}, 1, 1],
+  ['delta inherited for single base unit', 'delta_celsius', {temperature: {definition: 'K'}}, 1, 1],
+  ['delta inherited for derived unit', 'delta_newton', {force: {definition: 'mN'}}, 1, 1000],
+  ['delta inherited when transforming to base units', 'delta_newton', {mass: {definition: 'kilogram'}, time: {definition: 'second'}, length: {definition: 'meter'}}, 1, 1]
 ]
 )('test conversion with "toSystem()": %s', async (name, unit, system, valueA, valueB) => {
   const a = new Quantity(valueA, unit)
@@ -122,23 +126,3 @@ test.each([
   }
   expect(valueA).toBeCloseTo(10 * valueB)
 })
-
-test.each([
-  ['number only', '100', undefined, true, false, {valueString: '100', value: 100}],
-  ['unit only', 'joule', null, false, true, {valueString: undefined, value: undefined, unit: new Unit('joule')}],
-  ['number and unit with dimension', '100 joule', 'energy', true, true, {valueString: '100', value: 100, unit: new Unit('joule')}],
-  ['number and unit without dimension', '100 joule', null, true, true, {valueString: '100', value: 100, unit: new Unit('joule')}],
-  ['incorrect dimension', '100 joule', 'length', true, true, {valueString: '100', value: 100, unit: new Unit('joule'), error: 'Unit "joule" is incompatible with dimension "length".'}],
-  ['missing unit', '100', 'length', true, true, {valueString: '100', value: 100, unit: undefined, error: 'Unit is required.'}],
-  ['missing value', 'joule', 'energy', true, true, {valueString: undefined, value: undefined, unit: new Unit('joule'), error: 'Enter a valid numerical value.'}],
-  ['mixing number and quantity #1', '1 / joule', 'energy^-1', false, false, {valueString: '1', value: 1, unit: new Unit('1 / joule')}],
-  ['mixing number and quantity #2', '100 / joule', 'energy^-1', false, false, {valueString: '100', value: 100, unit: new Unit('1 / joule')}]
-
-]
-)('test parseQuantity: %s', async (name, input, dimension, requireValue, requireUnit, expected) => {
-  const result = parseQuantity(input, dimension, requireValue, requireUnit)
-  expect(result.valueString === expected.valueString).toBe(true)
-  expect(result.value === expected.value).toBe(true)
-  expect(result.unit?.label() === expected.unit?.label()).toBe(true)
-  expect(result.error === expected.error).toBe(true)
-})
diff --git a/gui/src/components/units/Unit.js b/gui/src/components/units/Unit.js
index 220ac7bc8e3aeb3437b357abfb47510ebb07149c..58ab8e6810c21d5483b771c3ec3fc8eea2c9e087 100644
--- a/gui/src/components/units/Unit.js
+++ b/gui/src/components/units/Unit.js
@@ -18,6 +18,7 @@
 import {isNil, has, isString} from 'lodash'
 import {Unit as UnitMathJS} from 'mathjs'
 import {unitToAbbreviationMap} from './UnitContext'
+import {parseInternal} from './common'
 
 export const DIMENSIONLESS = 'dimensionless'
 
@@ -35,8 +36,7 @@ export class Unit {
    */
   constructor(unit) {
     if (isString(unit)) {
-      unit = normalizeExpression(unit)
-      unit = new UnitMathJS(undefined, unit)
+      unit = parseInternal(unit, {requireUnit: true}).unit
     } else if (unit instanceof Unit) {
       unit = unit.mathjsUnit.clone()
     } else if (unit instanceof UnitMathJS) {
@@ -45,8 +45,6 @@ export class Unit {
       throw Error('Please provide the unit as a string or as an instance of Unit.')
     }
     this.mathjsUnit = unit
-    // this._labelabbreviate = undefined
-    // this._label = undefined
   }
 
   /**
@@ -56,7 +54,6 @@ export class Unit {
    */
   equalBase(unit) {
     if (isString(unit)) {
-      unit = normalizeExpression(unit)
       unit = new Unit(unit)
     }
     return this.mathjsUnit.equalBase(unit.mathjsUnit)
@@ -70,19 +67,22 @@ export class Unit {
    * (as given or defined by the unit system) are used.
    * @returns A string representing the unit.
    */
-  label(abbreviate = true) {
+  label(abbreviate = true, showDelta = false) {
     // TODO: The label caching is disabled for now. Because Quantities are
     // stored as recoil.js atoms, they become immutable which causes problems
     // with internal state mutation.
-    // if (this._labelabbreviate === abbreviate && this._label) {
-    //   return this._label
-    // }
     const units = this.mathjsUnit.units
     let strNum = ''
     let strDen = ''
     let nNum = 0
     let nDen = 0
 
+    function getDelta(unit) {
+      return (showDelta && unit.delta)
+        ? abbreviate ? 'Δ' : 'delta_'
+        : ''
+    }
+
     function getName(unit) {
       if (unit.base.key === DIMENSIONLESS) return ''
       return abbreviate
@@ -90,7 +90,7 @@ export class Unit {
         : unit.name
     }
 
-    function getPrefix(unit, original) {
+    function getPrefix(original) {
       if (!abbreviate) return original
       const prefixMap = {
         // SI
@@ -128,32 +128,36 @@ export class Unit {
     }
 
     for (let i = 0; i < units.length; i++) {
-      if (units[i].power > 0) {
+      const unitDef = units[i]
+      if (unitDef.power > 0) {
         nNum++
-        const prefix = getPrefix(units[i].unit.name, units[i].prefix.name)
-        const name = getName(units[i].unit)
-        strNum += ` ${prefix}${name}`
-        if (Math.abs(units[i].power - 1.0) > 1e-15) {
-          strNum += '^' + units[i].power
+        const prefix = getPrefix(unitDef.prefix.name)
+        const name = getName(unitDef.unit)
+        const delta = getDelta(unitDef)
+        strNum += ` ${delta}${prefix}${name}`
+        if (Math.abs(unitDef.power - 1.0) > 1e-15) {
+          strNum += '^' + unitDef.power
         }
-      } else if (units[i].power < 0) {
+      } else if (unitDef.power < 0) {
         nDen++
       }
     }
 
     if (nDen > 0) {
       for (let i = 0; i < units.length; i++) {
-        if (units[i].power < 0) {
-          const prefix = getPrefix(units[i].unit.name, units[i].prefix.name)
-          const name = getName(units[i].unit)
+        const unitDef = units[i]
+        if (unitDef.power < 0) {
+          const prefix = getPrefix(unitDef.prefix.name)
+          const name = getName(unitDef.unit)
+          const delta = getDelta(unitDef)
           if (nNum > 0) {
-            strDen += ` ${prefix}${name}`
-            if (Math.abs(units[i].power + 1.0) > 1e-15) {
-              strDen += '^' + (-units[i].power)
+            strDen += ` ${delta}${prefix}${name}`
+            if (Math.abs(unitDef.power + 1.0) > 1e-15) {
+              strDen += '^' + (-unitDef.power)
             }
           } else {
-            strDen += ` ${prefix}${name}`
-            strDen += '^' + (units[i].power)
+            strDen += ` ${delta}${prefix}${name}`
+            strDen += '^' + (unitDef.power)
           }
         }
       }
@@ -177,8 +181,6 @@ export class Unit {
     }
     str += strDen
 
-    // this._labelabbreviate = abbreviate
-    // this._label = str
     return str
   }
 
@@ -223,20 +225,28 @@ export class Unit {
    * @returns A new Unit expressed in the given units.
    */
   to(unit) {
-    if (isString(unit)) {
-      unit = normalizeExpression(unit)
-    } else if (unit instanceof Unit) {
-      unit = unit.label()
+    let mathjsUnit
+    if (unit instanceof Unit) {
+      mathjsUnit = unit.mathjsUnit
+    } else if (isString(unit)) {
+      mathjsUnit = parseInternal(unit, {requireUnit: true}).unit
     } else {
       throw Error('Unknown unit type. Please provide the unit as as string or as instance of Unit.')
     }
+    return new Unit(this.mathjsUnit.to(mathjsUnit))
+  }
 
-    // We cannot directly feed the unit string into Math.js, because it will try
-    // to parse units like 1/<unit> as Math.js units which have values, and then
-    // will raise an exception when converting between valueless and valued
-    // unit. The workaround is to explicitly define a valueless unit.
-    unit = new UnitMathJS(undefined, unit === '' ? DIMENSIONLESS : unit)
-    return new Unit(this.mathjsUnit.to(unit))
+  /**
+   * Converts all units to their delta versions.
+   *
+   * @returns A new Unit with delta units.
+   */
+  toDelta() {
+    const unitMathJS = this.mathjsUnit.clone()
+    for (const unit of unitMathJS.units) {
+      unit.delta = true
+    }
+    return new Unit(unitMathJS)
   }
 
   /**
@@ -295,6 +305,9 @@ export class Unit {
    * present, it will, however, attempt to convert it to the base units. Any
    * further simplication is not performed.
    *
+   * If the converted unit has any delta-units, the converted units will also
+   * become delta-units.
+   *
    * @param {object} system The target unit system.
    * @returns A new Unit instance in the given system.
    */
@@ -310,11 +323,12 @@ export class Unit {
     for (const unit of this.mathjsUnit.units) {
       const dimension = unit.unit.base.key
       const newUnitDefinition = system?.[dimension]?.definition
+
       // If the unit for this dimension is defined, use it
       if (!isNil(newUnitDefinition)) {
         const newUnit = new Unit(newUnitDefinition)
         for (const unitDef of newUnit.mathjsUnit.units) {
-          proposedUnitList.push({...unitDef, power: unitDef.power * unit.power})
+          proposedUnitList.push({...unitDef, power: unitDef.power * unit.power, delta: unit.delta})
         }
       // Otherwise convert to base units
       } else {
@@ -328,7 +342,8 @@ export class Unit {
               proposedUnitList.push({
                 unit: UNITS[system[baseDim].definition],
                 prefix: PREFIXES.NONE[''],
-                power: unit.power ? newDimensions[i] * unit.power : 0
+                power: unit.power ? newDimensions[i] * unit.power : 0,
+                delta: unit.delta
               })
             } else {
               missingBaseDim = true
@@ -346,22 +361,3 @@ export class Unit {
     return new Unit(ret)
   }
 }
-
-/**
- * Normalizes the given expression into a format that can be parsed by MathJS.
- *
- * This function will replace the Pint power symbol of '**' with the symbol
- * '^' used by MathJS. In addition, we convert any 'delta'-units (see:
- * https://pint.readthedocs.io/en/stable/nonmult.html) into their regular
- * counterparts: MathJS will automatically ignore the offset when using
- * non-multiplicative units in expressions.
- *
- * @param {str} expression Expression
- * @returns string Expression in normalized form
- */
-export function normalizeExpression(expression) {
-  let normalized = expression.replace(/\*\*/g, '^')
-  normalized = normalized.replace(/delta_/g, '')
-  normalized = normalized.replace(/Δ/g, '')
-  return normalized
-}
diff --git a/gui/src/components/units/Unit.spec.js b/gui/src/components/units/Unit.spec.js
index 7cd88133bc700fbb8a531c4b5726dbc17959d7c9..b79b0868e7598e3f8c3a32ea4dd84188e1d3bbc1 100644
--- a/gui/src/components/units/Unit.spec.js
+++ b/gui/src/components/units/Unit.spec.js
@@ -55,11 +55,14 @@ test.each([
   ['preserve order', 'second*meter', 's m'],
   ['power', 'meter^2', 'm^2'],
   ['negative power', 'meter^-1', 'm^-1'],
-  ['chain', 'meter*meter/second^2', '(m m) / s^2']
+  ['chain', 'meter*meter/second^2', '(m m) / s^2'],
+  ['delta long', 'delta_celsius', 'Δ°C'],
+  ['delta short', 'Δcelsius', 'Δ°C'],
+  ['delta with prefix', 'delta_millicelsius', 'Δm°C']
 ]
 )('label abbreviation: %s', async (name, unit, label) => {
   const a = new Unit(unit)
-  expect(a.label()).toBe(label)
+  expect(a.label(true, true)).toBe(label)
 })
 
 test.each([
@@ -87,10 +90,10 @@ test.each([
   ['power with hat', 'm^2', 'angstrom^2', 'Ã…^2'],
   ['power with double asterisk (single)', 'm**2', 'angstrom**2', 'Ã…^2'],
   ['power with double asterisk (multiple)', 'm**2 / s**2', 'angstrom**2 / ms**2', 'Ã…^2 / ms^2'],
-  ['explicit delta (single)', 'delta_celsius', 'delta_K', 'K'],
-  ['explicit delta (multiple)', 'delta_celsius / delta_celsius', 'delta_K / delta_K', 'K / K'],
-  ['explicit delta symbol (single)', 'Δcelsius', 'ΔK', 'K'],
-  ['explicit delta symbol (multiple)', 'Δcelsius / Δcelsius', 'ΔK / ΔK', 'K / K'],
+  ['explicit delta (single)', 'delta_celsius', 'delta_K', 'ΔK'],
+  ['explicit delta (multiple)', 'delta_celsius / delta_celsius', 'delta_K / delta_K', 'ΔK / ΔK'],
+  ['explicit delta symbol (single)', 'Δcelsius', 'K', 'K'],
+  ['explicit delta symbol (multiple)', 'Δcelsius / Δcelsius', 'ΔK / ΔK', 'ΔK / ΔK'],
   ['combined', 'm*m/s^2', 'angstrom^2/femtosecond^2', 'Ã…^2 / fs^2'],
   ['negative exponent', 's^-2', 'femtosecond^-2', 'fs^-2'],
   ['simple to complex with one unit', 'N', 'kg*m/s^2', '(kg m) / s^2'],
@@ -102,7 +105,7 @@ test.each([
 )('test conversion with "to()": %s', async (name, unitA, unitB, labelB) => {
   const a = new Unit(unitA)
   const b = a.to(unitB)
-  expect(b.label()).toBe(labelB)
+  expect(b.label(true, true)).toBe(labelB)
 })
 
 test.each([
@@ -113,12 +116,15 @@ test.each([
   ['combination', 'a_u_force * angstrom', {force: {definition: 'newton'}, length: {definition: 'meter'}}, 'N m'],
   ['use base units if derived unit not defined in system', 'newton * meter', {mass: {definition: 'kilogram'}, time: {definition: 'second'}, length: {definition: 'meter'}}, '(kg m m) / s^2'],
   ['unit definition with prefix', 'kg^2', {mass: {definition: 'mg'}}, 'mg^2'],
-  ['expression as definition', 'N', {force: {definition: '(kg m) / s^2'}}, '(kg m) / s^2']
+  ['expression as definition', 'N', {force: {definition: '(kg m) / s^2'}}, '(kg m) / s^2'],
+  ['delta inherited for single base unit', 'delta_celsius', {temperature: {definition: 'K'}}, 'ΔK'],
+  ['delta inherited for derived unit', 'delta_newton', {force: {definition: 'mN'}}, 'ΔmN'],
+  ['delta inherited when transforming to base units', 'delta_newton', {mass: {definition: 'kilogram'}, time: {definition: 'second'}, length: {definition: 'meter'}}, '(Δkg Δm) / Δs^2']
 ]
 )('test conversion with "toSystem()": %s', async (name, unit, system, label) => {
   const a = new Unit(unit)
   const b = a.toSystem(system)
-  expect(b.label()).toBe(label)
+  expect(b.label(true, true)).toBe(label)
 })
 
 test.each([
diff --git a/gui/src/components/units/UnitContext.js b/gui/src/components/units/UnitContext.js
index f31b3a5cb24e27cb979953905108f4b43fa5bd8b..f946f6158d8b592e513bf77ceb81c79fb2813164 100644
--- a/gui/src/components/units/UnitContext.js
+++ b/gui/src/components/units/UnitContext.js
@@ -39,7 +39,7 @@ UnitMathJS.PREFIXES.PINT = prefixes
 // Customize the unit parsing to allow certain special symbols
 const isAlphaOriginal = UnitMathJS.isValidAlpha
 const isSpecialChar = function(c) {
-  const specialChars = new Set(['_', 'Å', 'Å', 'å', '°', 'µ', 'ö', 'é', '∞'])
+  const specialChars = new Set(['_', 'Å', 'Å', 'å', '°', 'µ', 'ö', 'é', '∞', 'Δ'])
   return specialChars.has(c)
 }
 const isGreekLetter = function(c) {
@@ -50,7 +50,7 @@ UnitMathJS.isValidAlpha = function(c) {
   return isAlphaOriginal(c) || isSpecialChar(c) || isGreekLetter(c)
 }
 
-// Create MathJS unit definitions from the data exported by 'nomad dev units'
+// Create MathJS unit definitions from the data exported by 'nomad dev units'.
 export const unitToAbbreviationMap = {}
 const unitDefinitions = {}
 for (let def of unitList) {
diff --git a/gui/src/components/units/UnitInput.js b/gui/src/components/units/UnitInput.js
index bddff7a034e939ae3b58ce033a27d25297e163d7..327fe3d97b012c852cec2b791fa5bf368ae2affc 100644
--- a/gui/src/components/units/UnitInput.js
+++ b/gui/src/components/units/UnitInput.js
@@ -21,7 +21,7 @@ import PropTypes from 'prop-types'
 import {isNil} from 'lodash'
 import {getSuggestions} from '../../utils'
 import {unitMap} from './UnitContext'
-import {parseQuantity} from './Quantity'
+import {parse} from './common'
 import {List, ListItemText, ListSubheader, makeStyles} from '@material-ui/core'
 import {VariableSizeList} from 'react-window'
 import {InputText} from '../search/input/InputText'
@@ -80,7 +80,7 @@ export const UnitInput = React.memo(({value, error, onChange, onAccept, onSelect
         return {valid: false, error: 'Please specify a value'}
       }
     }
-    const {error, unit} = parseQuantity(value, dimension, false, true)
+    const {error, unit} = parse(value, {dimension, requireUnit: true})
     return {valid: !error, error, data: unit}
   }, [optional, dimension])
 
diff --git a/gui/src/components/units/common.js b/gui/src/components/units/common.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0a6e46b56c29c2c0688862b54421b2d4ec152ce
--- /dev/null
+++ b/gui/src/components/units/common.js
@@ -0,0 +1,444 @@
+/*
+ * Copyright The NOMAD Authors.
+ *
+ * This file is part of NOMAD. See https://nomad-lab.eu for further info.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {memoize, has} from 'lodash'
+import {Unit} from './Unit'
+import { Unit as UnitMathJS } from 'mathjs'
+
+export const deltaPrefixes = ['delta_', 'Δ']
+
+/**
+ * Convenience function for parsing value and unit information from a string.
+ * Can parse values, units or both at the same time.
+ *
+ * @param {string} input The input string to parse
+ * @param {object} options Parsing options. These include:
+ *   - dimension: Dimension for the unit. Note that you should use the base
+ *       dimensions which you can get e.g. with .dimension(true).
+ *   - requireValue: Whether an explicit numeric value is required at the start
+  *      of the input.
+ *   - requireUnit: Whether an explicit unit in the input is required.
+ * @returns Object containing the following properties, if available:
+ *  - value: Numerical value as a number
+ *  - valueString: The original number input as a string. Note that this can only return
+ *      the number when it is used as a prefix, and does not work with numbers
+ *      that are part of a complex expression, e.g. 300 eV / 1000 K.
+ *  - unit: Unit instance
+ *  - error: Error messsage
+ */
+export function parse(input, options) {
+  options = {
+    dimension: null,
+    requireValue: false,
+    requireUnit: false,
+    ...options
+  } || {}
+
+  const result = {}
+
+  try {
+    const parseResults = parseInternal(input, options)
+    result.value = parseResults.value || undefined
+    result.valueString = parseResults.valueString || undefined
+    if (parseResults.unit?.units?.length) {
+      result.unit = new Unit(parseResults.unit)
+    }
+  } catch (e) {
+    result.error = e.message
+    return result
+  }
+
+  // TODO: This check is not enough: the input may be compatible after the base
+  // units are compared.
+  if (options.dimension !== null && result.unit) {
+    if (!(result.unit.dimension(true) === options.dimension || result.unit.dimension(false) === options.dimension)) {
+      result.error = `Unit "${result.unit.label(false)}" is incompatible with dimension "${options.dimension}".`
+    }
+  }
+
+  return result
+}
+
+/**
+ * Parse a string into an optional value and a MathJS Unit definition.
+ *
+ * Throws an exception if the provided string does not contain a valid unit or
+ * cannot be parsed.
+ *
+ * @memberof Unit
+ * @param {string} str A string like "5.2 inch", "4e2 cm/s^2"
+ * @param {object} options Parsing options. These include:
+ *   - requireValue: Whether an explicit numeric value is required at the start
+  *      of the input.
+ *   - requireUnit: Whether an explicit unit in the input is required.
+ * @returns Object containing the following properties, if available:
+ *  - value: Numerical value as a number
+ *  - valueString: The original number input as a string. Note that this can only return
+ *      the number when it is used as a prefix, and does not work with numbers
+ *      that are part of a complex expression, e.g. 300 eV / 1000 K.
+ *  - unit: Unit instance
+ */
+export function parseInternal(str, options) {
+  // Replace ** with ^
+  str = str.replace(/\*\*/g, '^')
+
+  options = options || {}
+  const text = str
+  let index = -1
+  let c = ''
+
+  function skipWhitespace() {
+    while (c === ' ' || c === '\t') {
+      next()
+    }
+  }
+
+  function isDigitDot(c) {
+    return ((c >= '0' && c <= '9') || c === '.')
+  }
+
+  function isDigit(c) {
+    return ((c >= '0' && c <= '9'))
+  }
+
+  function next() {
+    index++
+    c = text.charAt(index)
+  }
+
+  function revert(oldIndex) {
+    index = oldIndex
+    c = text.charAt(index)
+  }
+
+  function parseUnit() {
+    let unitName = ''
+
+    // Alphanumeric characters only; matches [a-zA-Z0-9]
+    while (isDigit(c) || UnitMathJS.isValidAlpha(c)) {
+      unitName += c
+      next()
+    }
+
+    // Must begin with [a-zA-Z]
+    const firstC = unitName.charAt(0)
+    if (UnitMathJS.isValidAlpha(firstC)) {
+      return unitName
+    } else {
+      return null
+    }
+  }
+
+  function parseCharacter(toFind) {
+    if (c === toFind) {
+      next()
+      return toFind
+    } else {
+      return null
+    }
+  }
+
+  function parseNumber() {
+    let number = ''
+    const oldIndex = index
+
+    if (c === '+') {
+      next()
+    } else if (c === '-') {
+      number += c
+      next()
+    }
+
+    if (!isDigitDot(c)) {
+      // a + or - must be followed by a digit
+      revert(oldIndex)
+      return null
+    }
+
+    // get number, can have a single dot
+    if (c === '.') {
+      number += c
+      next()
+      if (!isDigit(c)) {
+        // this is no legal number, it is just a dot
+        revert(oldIndex)
+        return null
+      }
+    } else {
+      while (isDigit(c)) {
+        number += c
+        next()
+      }
+      if (c === '.') {
+        number += c
+        next()
+      }
+    }
+    while (isDigit(c)) {
+      number += c
+      next()
+    }
+
+    // check for exponential notation like "2.3e-4" or "1.23e50"
+    if (c === 'E' || c === 'e') {
+      // The grammar branches here. This could either be part of an exponent or the start of a unit that begins with the letter e, such as "4exabytes"
+
+      let tentativeNumber = ''
+      const tentativeIndex = index
+
+      tentativeNumber += c
+      next()
+
+      if (c === '+' || c === '-') {
+        tentativeNumber += c
+        next()
+      }
+
+      // Scientific notation MUST be followed by an exponent (otherwise we assume it is not scientific notation)
+      if (!isDigit(c)) {
+        // The e or E must belong to something else, so return the number without the e or E.
+        revert(tentativeIndex)
+        return number
+      }
+
+      // We can now safely say that this is scientific notation.
+      number = number + tentativeNumber
+      while (isDigit(c)) {
+        number += c
+        next()
+      }
+    }
+
+    return number
+  }
+
+  if (typeof text !== 'string') {
+    throw new TypeError('Invalid argument in Unit.parse, string expected')
+  }
+
+  const unit = new UnitMathJS()
+  unit.units = []
+
+  let powerMultiplierCurrent = 1
+  let expectingUnit = false
+
+  // A unit should follow this pattern:
+  // [number] ...[ [*/] unit[^number] ]
+  // unit[^number] ... [ [*/] unit[^number] ]
+
+  // Rules:
+  // number is any floating point number.
+  // unit is any alphanumeric string beginning with an alpha. Units with names like e3 should be avoided because they look like the exponent of a floating point number!
+  // The string may optionally begin with a number.
+  // Each unit may optionally be followed by ^number.
+  // Whitespace or a forward slash is recommended between consecutive units, although the following technically is parseable:
+  //   2m^2kg/s^2
+  // it is not good form. If a unit starts with e, then it could be confused as a floating point number:
+  //   4erg
+
+  next()
+  skipWhitespace()
+
+  // Optional number at the start of the string
+  const valueString = parseNumber()
+  let value = null
+  if (valueString) {
+    value = parseFloat(valueString)
+    skipWhitespace() // Whitespace is not required here
+
+    // handle multiplication or division right after the value, like '1/s'
+    if (parseCharacter('*')) {
+      powerMultiplierCurrent = 1
+      expectingUnit = true
+    } else if (parseCharacter('/')) {
+      powerMultiplierCurrent = -1
+      expectingUnit = true
+    }
+  } else if (options.requireValue) {
+    throw new SyntaxError('Enter a valid numerical value')
+  }
+
+  // Stack to keep track of powerMultipliers applied to each parentheses group
+  const powerMultiplierStack = []
+
+  // Running product of all elements in powerMultiplierStack
+  let powerMultiplierStackProduct = 1
+
+  while (true) {
+    skipWhitespace()
+
+    // Check for and consume opening parentheses, pushing powerMultiplierCurrent to the stack
+    // A '(' will always appear directly before a unit.
+    while (c === '(') {
+      powerMultiplierStack.push(powerMultiplierCurrent)
+      powerMultiplierStackProduct *= powerMultiplierCurrent
+      powerMultiplierCurrent = 1
+      next()
+      skipWhitespace()
+    }
+
+    // Is there something here?
+    let uStr
+    if (c) {
+      const oldC = c
+      uStr = parseUnit()
+      if (uStr === null) {
+        throw new SyntaxError('Unexpected "' + oldC + '" in "' + text + '" at index ' + index.toString())
+      }
+    } else {
+      // End of input.
+      break
+    }
+
+    // Verify the unit exists and get the prefix (if any)
+    const res = findUnit(uStr)
+    if (res === null) {
+      // Unit not found.
+      throw new SyntaxError('Unit "' + uStr + '" not found.')
+    }
+
+    let power = powerMultiplierCurrent * powerMultiplierStackProduct
+    // Is there a "^ number"?
+    skipWhitespace()
+    if (parseCharacter('^')) {
+      skipWhitespace()
+      const p = parseNumber()
+      if (p === null) {
+        // No valid number found for the power!
+        throw new SyntaxError('In "' + str + '", "^" must be followed by a floating-point number')
+      }
+      power *= p
+    }
+
+    // Add the unit to the list
+    unit.units.push({
+      unit: res.unit,
+      prefix: res.prefix,
+      delta: res.delta,
+      power
+    })
+    for (let i = 0; i < UnitMathJS.BASE_DIMENSIONS.length; i++) {
+      unit.dimensions[i] += (res.unit.dimensions[i] || 0) * power
+    }
+
+    // Check for and consume closing parentheses, popping from the stack.
+    // A ')' will always follow a unit.
+    skipWhitespace()
+    while (c === ')') {
+      if (powerMultiplierStack.length === 0) {
+        throw new SyntaxError('Unmatched ")" in "' + text + '" at index ' + index.toString())
+      }
+      powerMultiplierStackProduct /= powerMultiplierStack.pop()
+      next()
+      skipWhitespace()
+    }
+
+    // "*" and "/" should mean we are expecting something to come next.
+    // Is there a forward slash? If so, negate powerMultiplierCurrent. The next unit or paren group is in the denominator.
+    expectingUnit = false
+
+    if (parseCharacter('*')) {
+      // explicit multiplication
+      powerMultiplierCurrent = 1
+      expectingUnit = true
+    } else if (parseCharacter('/')) {
+      // division
+      powerMultiplierCurrent = -1
+      expectingUnit = true
+    } else {
+      // implicit multiplication
+      powerMultiplierCurrent = 1
+    }
+
+    // Replace the unit into the auto unit system
+    if (res.unit.base) {
+      const baseDim = res.unit.base.key
+      UnitMathJS.UNIT_SYSTEMS.auto[baseDim] = {
+        unit: res.unit,
+        prefix: res.prefix
+      }
+    }
+  }
+
+  // Has the string been entirely consumed?
+  skipWhitespace()
+  if (c) {
+    throw new SyntaxError('Could not parse: "' + str + '"')
+  }
+
+  // Is there a trailing slash?
+  if (expectingUnit) {
+    throw new SyntaxError('Trailing characters: "' + str + '"')
+  }
+
+  // Is the parentheses stack empty?
+  if (powerMultiplierStack.length !== 0) {
+    throw new SyntaxError('Unmatched "(" in "' + text + '"')
+  }
+
+  // Are there any units at all?
+  if (unit.units.length === 0 && options.requireUnit) {
+    throw new SyntaxError('Unit is required')
+  }
+
+  return {value, valueString, unit}
+}
+
+/**
+ * Find a unit from a string
+ *
+ * @param {string} str A string like 'cm' or 'inch'
+ * @returns {Object | null} When found, an object with fields unit and
+*    prefix is returned. Else, null is returned.
+ */
+const findUnit = memoize((str) => {
+  // First, match units names exactly. For example, a user could define 'mm' as
+  // 10^-4 m, which is silly, but then we would want 'mm' to match the
+  // user-defined unit.
+  if (has(UnitMathJS.UNITS, str)) {
+    const unit = UnitMathJS.UNITS[str]
+    const prefix = unit.prefixes['']
+    return { unit, prefix, delta: false }
+  }
+
+  for (const name in UnitMathJS.UNITS) {
+    if (has(UnitMathJS.UNITS, name)) {
+      if (str.endsWith(name)) {
+        const unit = UnitMathJS.UNITS[name]
+        const prefixLen = (str.length - name.length)
+        let prefixName = str.substring(0, prefixLen)
+        let delta = false
+        for (const deltaPrefix of deltaPrefixes) {
+          if (prefixName.startsWith(deltaPrefix)) {
+            prefixName = prefixName.substring(deltaPrefix.length)
+            delta = true
+            break
+          }
+        }
+        const prefix = has(unit.prefixes, prefixName)
+          ? unit.prefixes[prefixName]
+          : undefined
+        if (prefix !== undefined) {
+          return { unit, prefix, delta }
+        }
+      }
+    }
+  }
+
+  return null
+})
diff --git a/gui/src/components/units/common.spec.js b/gui/src/components/units/common.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..70c34826a5404e2df535533023238257f5d6892d
--- /dev/null
+++ b/gui/src/components/units/common.spec.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright The NOMAD Authors.
+ *
+ * This file is part of NOMAD. See https://nomad-lab.eu for further info.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Unit } from './Unit'
+import { parse } from './common'
+
+test.each([
+  ['number only', '100', {}, {valueString: '100', value: 100}],
+  ['complicated number 1', '-0.015e-10', {}, {valueString: '-0.015e-10', value: -0.015e-10}],
+  ['complicated number 2', '-.015E-10', {}, {valueString: '-.015E-10', value: -0.015e-10}],
+  ['unit only', 'joule', {}, {valueString: undefined, value: undefined, unit: new Unit('joule')}],
+  ['whitespaces', '  100  joule  ', {}, {valueString: '100', value: 100, unit: new Unit('joule') }],
+  ['number and unit with dimension', '100 joule', {}, {valueString: '100', value: 100, unit: new Unit('joule')}],
+  ['number and unit without dimension', '100 joule', {}, {valueString: '100', value: 100, unit: new Unit('joule')}],
+  ['incorrect dimension', '100 joule', {dimension: 'length'}, {valueString: '100', value: 100, unit: new Unit('joule'), error: 'Unit "joule" is incompatible with dimension "length".'}],
+  ['missing unit', '100', {requireUnit: true}, {error: 'Unit is required'}],
+  ['missing unit with dimension specified', '100', {dimension: 'energy', requireUnit: true}, {error: 'Unit is required'}],
+  ['missing value', 'joule', {requireValue: true}, {error: 'Enter a valid numerical value'}],
+  ['mixing number and quantity #1', '1 / joule', {dimension: 'energy^-1'}, {valueString: '1', value: 1, unit: new Unit('1 / joule')}],
+  ['mixing number and quantity #2', '100 / joule', {dimension: 'energy^-1'}, {valueString: '100', value: 100, unit: new Unit('1 / joule')}]
+
+]
+)('test parse: %s', async (name, input, options, expected) => {
+  const result = parse(input, options)
+  expect(result.valueString === expected.valueString).toBe(true)
+  expect(result.value === expected.value).toBe(true)
+  expect(result.unit?.label() === expected.unit?.label()).toBe(true)
+  expect(result.error === expected.error).toBe(true)
+})
diff --git a/gui/tests/artifacts.js b/gui/tests/artifacts.js
index fbf6ef833c2f1a957bb19d711322be48ba448ca4..cd1808ae2403b6250a27175e10f19c2ab4151157 100644
--- a/gui/tests/artifacts.js
+++ b/gui/tests/artifacts.js
@@ -96388,7 +96388,7 @@ window.nomadArtifacts = {
                   "type_data": "float64"
                 },
                 "shape": [],
-                "unit": "degree_Celsius / minute"
+                "unit": "delta_degree_Celsius / minute"
               },
               {
                 "m_def": "nomad.metainfo.metainfo.Quantity",
diff --git a/nomad/cli/dev.py b/nomad/cli/dev.py
index 13837892b2517738cde98716f2d95242da7d2da0..7c7db7a37f4e6b8d2556e0920a694ddf461634de 100644
--- a/nomad/cli/dev.py
+++ b/nomad/cli/dev.py
@@ -585,40 +585,4 @@ def _generate_units_json(all_metainfo) -> Tuple[Any, Any]:
     unit_list.sort(key=lambda x: x.get('name'))
     unit_list.sort(key=lambda x: 0 if x.get('definition') is None else 1)
 
-    # Go through the metainfo and check that all units are defined. Note that
-    # this will break if complex derived units are used in the metainfo. In
-    # this case they can only be validated in a GUI test.
-    unit_names = set()
-    for unit in unit_list:
-        unit_names.add(unit['name'])
-        for alias in unit.get('aliases', []):
-            unit_names.add(alias)
-
-    units = set()
-    for package in all_metainfo.packages:
-        for section in package.section_definitions:
-            for quantity in section.quantities:
-                unit = quantity.unit
-                if unit is not None:
-                    parts = str(unit).split()
-                    for part in parts:
-                        is_operator = part in {'/', '**', '*'}
-                        is_number = True
-                        try:
-                            int(part)
-                        except Exception:
-                            is_number = False
-                        if not is_operator and not is_number:
-                            units.add(part)
-
-    # Check that the defined units do not contain 'delta_' or 'Δ' in them. This is
-    # reserved to indicate that a quantity should be treated without offset.
-    # MathJS does not have explicit support for these delta-units, but instead
-    # uses them implicitly when non-multiplicative units appear in expressions.
-    for unit in unit_names:
-        assert 'delta_' not in unit and 'Δ' not in unit, (
-            f'Invalid unit name {unit}. "delta_" and "Δ" are reserved for unit variants '
-            'with no offset, but MathJS does not have support for these units.'
-        )
-
     return unit_list, prefixes
diff --git a/nomad/metainfo/metainfo.py b/nomad/metainfo/metainfo.py
index 9dfb5ffcf799d94328924c87eeb8e0e5a32db9de..99b5263a09a9a09089a13c239d65f202b98e2b42 100644
--- a/nomad/metainfo/metainfo.py
+++ b/nomad/metainfo/metainfo.py
@@ -64,7 +64,6 @@ from nomad.metainfo.util import (
     MTypes,
     ReferenceURL,
     SectionAnnotation,
-    _delta_symbols,
     check_dimensionality,
     check_unit,
     convert_to,
@@ -391,14 +390,21 @@ class _Unit(DataType):
         if quantity_def.flexible_unit:
             return None
 
-        value = value.__str__()
+        return value.__str__()
+
         # The delta prefixes are not serialized: only implicit deltas are
         # allowed currently.
-        return reduce(lambda a, b: a.replace(b, ''), _delta_symbols, value)
+        # return reduce(lambda a, b: a.replace(b, ''), _delta_symbols, value)
 
     def deserialize(self, section, quantity_def: Quantity, value):
         check_unit(value)
-        value = units.parse_units(value)
+
+        # The serialized version has the deltas always in correct locations, so
+        # we skip the automatic delta conversion. This way users can also choose
+        # to not use delta units with ureg.parse_units('celsius/hr',
+        # as_delta=False)
+        value = units.parse_units(value, as_delta=False)
+
         check_dimensionality(quantity_def, value)
         return value
 
diff --git a/nomad/metainfo/util.py b/nomad/metainfo/util.py
index cb195b8849e5abf855652fff3980cacf64e605a5..928480056b511d80313c6a1cdf3577d3bc2fc53f 100644
--- a/nomad/metainfo/util.py
+++ b/nomad/metainfo/util.py
@@ -49,7 +49,6 @@ except AttributeError:
 from nomad.units import ureg
 
 __hash_method = 'sha1'  # choose from hashlib.algorithms_guaranteed
-_delta_symbols = {'delta_', 'Δ'}
 
 
 @dataclass(frozen=True)
@@ -721,18 +720,9 @@ def check_dimensionality(quantity_def, unit: Optional[pint.Unit]) -> None:
 
 def check_unit(unit: Union[str, pint.Unit]) -> None:
     """Check that the unit is valid."""
-    if isinstance(unit, str):
-        unit_str = unit
-    elif isinstance(unit, pint.Unit):
-        unit_str = str(unit)
-    else:
+    if not isinstance(unit, (str, pint.Unit)):
         raise TypeError('Units must be given as str or pint Unit instances.')
 
-    # Explicitly providing a Pint delta-unit is not currently allowed.
-    # Implicit conversions are fine as MathJS on the frontend supports them.
-    if any(x in unit_str for x in _delta_symbols):
-        raise TypeError('Explicit Pint "delta"-units are not yet supported.')
-
 
 def to_section_def(section_def):
     """
diff --git a/tests/metainfo/test_metainfo.py b/tests/metainfo/test_metainfo.py
index a4f780ed1d234246ed16a2a43b55cbb8ca91db49..f4702c2528b2183573450facefd2856b47e71055 100644
--- a/tests/metainfo/test_metainfo.py
+++ b/tests/metainfo/test_metainfo.py
@@ -215,37 +215,58 @@ class TestM2:
         assert System.lattice_vectors.unit is not None
 
     @pytest.mark.parametrize(
-        'unit',
+        'unit, serialization',
         [
-            pytest.param('delta_degC / hr'),
-            pytest.param('ΔdegC / hr'),
-            pytest.param(ureg.delta_degC / ureg.hour),
-        ],
-    )
-    def test_unit_explicit_delta(self, unit):
-        """Explicit delta values are not allowed when setting or de-serializing."""
-        with pytest.raises(TypeError):
-            Quantity(type=np.dtype(np.float64), unit=unit)
-        with pytest.raises(TypeError):
-            Quantity.m_from_dict(
-                {'m_def': 'nomad.metainfo.metainfo.Quantity', 'unit': str(unit)}
-            )
-
-    @pytest.mark.parametrize(
-        'unit',
-        [
-            pytest.param('degC / hr'),
-            pytest.param(ureg.degC / ureg.hour),
+            pytest.param(
+                'delta_degC',
+                'delta_degree_Celsius',
+                id='explicit delta full, non-multiplicative, string',
+            ),
+            pytest.param(
+                'ΔdegC',
+                'delta_degree_Celsius',
+                id='explicit delta short, non-multiplicative, string',
+            ),
+            pytest.param(
+                'delta_degC / hr',
+                'delta_degree_Celsius / hour',
+                id='explicit delta full, multiplicative, string',
+            ),
+            pytest.param(
+                'ΔdegC / hr',
+                'delta_degree_Celsius / hour',
+                id='explicit delta short, multiplicative, string',
+            ),
+            pytest.param(
+                ureg.delta_degC / ureg.hour,
+                'delta_degree_Celsius / hour',
+                id='explicit delta, multiplicative, objects',
+            ),
+            pytest.param(
+                'degC / hr',
+                'delta_degree_Celsius / hour',
+                id='implicit delta, multiplicative, string',
+            ),
+            pytest.param('degC', 'degree_Celsius', id='no delta, non-multiplicative'),
+            pytest.param(
+                ureg.parse_units('degC / hr', as_delta=False),
+                'degree_Celsius / hour',
+                id='no delta, multiplicative, string',
+            ),
+            pytest.param(
+                ureg.degC / ureg.hour,
+                'degree_Celsius / hour',
+                id='no delta, multiplicative, objects',
+            ),
         ],
     )
-    def test_unit_implicit_delta(self, unit):
-        """Implicit delta values are allowed in setting and deserializing, delta
-        prefixes are not serialized.
-        """
+    def test_unit_delta(self, unit, serialization):
         quantity = Quantity(type=np.dtype(np.float64), unit=unit)
         serialized = quantity.m_to_dict()
-        assert serialized['unit'] == 'degree_Celsius / hour'
-        Quantity.m_from_dict(serialized)
+        deserialized = quantity.m_from_dict(serialized)
+
+        assert serialized['unit'] == serialization
+        assert str(deserialized.unit) == str(quantity.unit)
 
     @pytest.mark.parametrize(
         'dtype', [pytest.param(np.longlong), pytest.param(np.ulonglong)]