diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index 4767f54d998b40705b0f911596a5359c7c3b9ad3..ab6f226a66eb6fa1ae29ece24c7127b9a273122a 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -81,6 +81,11 @@ var AbstractField = Widget.extend({
      * If this flag is set to true, the list column name will be empty.
      */
     noLabel: false,
+    /**
+     * Currently only used in list view.
+     * If set, this value will be displayed as column name.
+     */
+    label: '',
     /**
      * Abstract field class
      *
diff --git a/addons/web/static/src/js/fields/abstract_field_owl.js b/addons/web/static/src/js/fields/abstract_field_owl.js
index fe908e6ce72549bd737e67cea94081cde6b39502..f97faae35ed3344373e572d12cfe8f5fccefcf9d 100644
--- a/addons/web/static/src/js/fields/abstract_field_owl.js
+++ b/addons/web/static/src/js/fields/abstract_field_owl.js
@@ -627,6 +627,11 @@ odoo.define('web.AbstractFieldOwl', function (require) {
      * If this flag is set to true, the list column name will be empty.
      */
     AbstractField.noLabel = false;
+    /**
+     * Currently only used in list view.
+     * If set, this value will be displayed as column name.
+     */
+    AbstractField.label = "";
 
     return AbstractField;
 });
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index 93576be93489804b2b5b13709f8d2bac7a9aac3f..98febd8efa879c4bbf3d21f3eedf27dcf22c3981 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -812,8 +812,11 @@ var ListRenderer = BasicRenderer.extend({
         var description = string || field.string;
         if (node.attrs.widget) {
             $th.addClass(' o_' + node.attrs.widget + '_cell');
-            if (this.state.fieldsInfo.list[name].Widget.prototype.noLabel) {
+            const FieldWidget = this.state.fieldsInfo.list[name].Widget;
+            if (FieldWidget.prototype.noLabel) {
                 description = '';
+            } else if (FieldWidget.prototype.label) {
+                description = FieldWidget.prototype.label;
             }
         }
         $th.text(description)
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 03e62488ba40aad2f8cb480754797dc0765ebccc..2ad91dcae40f514c1275620625cb1273be1c66e3 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -304,6 +304,38 @@ QUnit.module('Views', {
         list.destroy();
     });
 
+    QUnit.test('column names (noLabel, label, string and default)', async function (assert) {
+        assert.expect(4);
+
+        const FieldChar = fieldRegistry.get('char');
+        fieldRegistry.add('nolabel_char', FieldChar.extend({
+            noLabel: true,
+        }));
+        fieldRegistry.add('label_char', FieldChar.extend({
+            label: "Some static label",
+        }));
+
+        const list = await createView({
+            View: ListView,
+            model: 'foo',
+            data: this.data,
+            arch: `
+                <tree>
+                    <field name="display_name" widget="nolabel_char"/>
+                    <field name="foo" widget="label_char"/>
+                    <field name="int_field" string="My custom label"/>
+                    <field name="text"/>
+                </tree>`,
+        });
+
+        assert.strictEqual(list.$('thead th[data-name=display_name]').text(), '');
+        assert.strictEqual(list.$('thead th[data-name=foo]').text(), 'Some static label');
+        assert.strictEqual(list.$('thead th[data-name=int_field]').text(), 'My custom label');
+        assert.strictEqual(list.$('thead th[data-name=text]').text(), 'text field');
+
+        list.destroy();
+    });
+
     QUnit.test('simple editable rendering', async function (assert) {
         assert.expect(15);