diff --git a/addons/hr/static/src/js/many2one_avatar_employee.js b/addons/hr/static/src/js/many2one_avatar_employee.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8a05028eb33e6ccdf099641b4fe52f9a277222d
--- /dev/null
+++ b/addons/hr/static/src/js/many2one_avatar_employee.js
@@ -0,0 +1,66 @@
+odoo.define('hr.Many2OneAvatarEmployee', function (require) {
+    "use strict";
+
+    // This module defines a variant of the Many2OneAvatarUser field widget,
+    // to support many2one fields pointing to 'hr.employee'. It also defines the
+    // kanban version of this widget.
+    //
+    // Usage:
+    //   <field name="employee_id" widget="many2one_avatar_employee"/>
+
+    const { _t } = require('web.core');
+    const fieldRegistry = require('web.field_registry');
+    const { Many2OneAvatarUser, KanbanMany2OneAvatarUser } = require('mail.Many2OneAvatarUser');
+    const session = require('web.session');
+
+
+    const Many2OneAvatarEmployeeMixin = {
+        supportedModel: 'hr.employee',
+
+        /**
+         * Set the field to read on 'hr.employee' to get the partner id.
+         *
+         * @override
+         */
+        init() {
+            this._super(...arguments);
+            this.partnerField = 'user_partner_id';
+        },
+
+        //----------------------------------------------------------------------
+        // Private
+        //----------------------------------------------------------------------
+
+        /**
+         * Display a warning if the user clicked on himself, or on an employee
+         * not associated with any user.
+         *
+         * @override
+         * @param {number} [partnerId] the id of the clicked partner
+         */
+        _displayWarning(partnerId) {
+            if (partnerId !== session.partner_id) {
+                // this is not ourself, so if we get here it means that the
+                // employee is not associated with any user
+                this.displayNotification({
+                    title: _t('No user to chat with'),
+                    message: _t('You can only chat with employees that have a dedicated user.'),
+                    type: 'info',
+                });
+            } else {
+                this._super(...arguments);
+            }
+        },
+    };
+
+    const Many2OneAvatarEmployee = Many2OneAvatarUser.extend(Many2OneAvatarEmployeeMixin);
+    const KanbanMany2OneAvatarEmployee = KanbanMany2OneAvatarUser.extend(Many2OneAvatarEmployeeMixin);
+
+    fieldRegistry.add('many2one_avatar_employee', Many2OneAvatarEmployee);
+    fieldRegistry.add('kanban.many2one_avatar_employee', KanbanMany2OneAvatarEmployee);
+
+    return {
+        Many2OneAvatarEmployee,
+        KanbanMany2OneAvatarEmployee,
+    };
+});
diff --git a/addons/hr/static/tests/many2one_avatar_employee_tests.js b/addons/hr/static/tests/many2one_avatar_employee_tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..de3580eaedb037332be983da1741bf2c9b8f6706
--- /dev/null
+++ b/addons/hr/static/tests/many2one_avatar_employee_tests.js
@@ -0,0 +1,218 @@
+odoo.define('hr.Many2OneAvatarEmployeeTests', function (require) {
+"use strict";
+
+const FormView = require('web.FormView');
+const KanbanView = require('web.KanbanView');
+const ListView = require('web.ListView');
+const { Many2OneAvatarEmployee } = require('hr.Many2OneAvatarEmployee');
+const { createView, dom, mock } = require('web.test_utils');
+
+
+QUnit.module('hr', {}, function () {
+    QUnit.module('Many2OneAvatarEmployee', {
+        beforeEach: function () {
+            // reset the cache before each test
+            Many2OneAvatarEmployee.prototype.partnerIds = {};
+
+            this.data = {
+                'foo': {
+                    fields: {
+                        employee_id: { string: "Employee", type: 'many2one', relation: 'hr.employee' },
+                    },
+                    records: [
+                        { id: 1, employee_id: 11 },
+                        { id: 2, employee_id: 7 },
+                        { id: 3, employee_id: 11 },
+                        { id: 4, employee_id: 23 },
+                    ],
+                },
+                'hr.employee': {
+                    fields: {
+                        display_name: { string: "Name", type: "char" },
+                        user_partner_id: { string: "Partner", type: "many2one", relation: 'res.partner' },
+                    },
+                    records: [{
+                        id: 11,
+                        name: "Mario",
+                        user_partner_id: 1,
+                    }, {
+                        id: 7,
+                        name: "Luigi",
+                        user_partner_id: 2,
+                    }, {
+                        id: 23,
+                        name: "Yoshi",
+                        user_partner_id: 3,
+                    }],
+                },
+                'res.partner': {
+                    fields: {
+                        display_name: { string: "Name", type: "char" },
+                    },
+                    records: [{
+                        id: 1,
+                        display_name: "Partner 1",
+                    }, {
+                        id: 2,
+                        display_name: "Partner 2",
+                    }, {
+                        id: 3,
+                        display_name: "Partner 3",
+                    }],
+                },
+            };
+        },
+    });
+
+    QUnit.test('many2one_avatar_employee widget in list view', async function (assert) {
+        assert.expect(7);
+
+        const list = await createView({
+            View: ListView,
+            model: 'foo',
+            data: this.data,
+            arch: '<tree><field name="employee_id" widget="many2one_avatar_employee"/></tree>',
+            mockRPC(route, args) {
+                if (args.method === 'read') {
+                    assert.step(`read ${args.model} ${args.args[0]}`);
+                }
+                return this._super(...arguments);
+            },
+        });
+
+        mock.intercept(list, 'call_service', ev => {
+            if (ev.data.service === 'mail_service') {
+                assert.step(`call service ${ev.data.method} ${ev.data.args[0]}`);
+            }
+        }, true);
+
+        assert.strictEqual(list.$('.o_data_cell span').text(), 'MarioLuigiMarioYoshi');
+
+        await dom.click(list.$('.o_data_cell:nth(0) .o_m2o_avatar'));
+        await dom.click(list.$('.o_data_cell:nth(1) .o_m2o_avatar'));
+        await dom.click(list.$('.o_data_cell:nth(2) .o_m2o_avatar'));
+
+
+        assert.verifySteps([
+            'read hr.employee 11',
+            'call service openDMChatWindow 1',
+            'read hr.employee 7',
+            'call service openDMChatWindow 2',
+            'call service openDMChatWindow 1',
+        ]);
+
+        list.destroy();
+    });
+
+    QUnit.test('many2one_avatar_employee widget in kanban view', async function (assert) {
+        assert.expect(6);
+
+        const kanban = await createView({
+            View: KanbanView,
+            model: 'foo',
+            data: this.data,
+            arch: `
+                <kanban>
+                    <templates>
+                        <t t-name="kanban-box">
+                            <div>
+                                <field name="employee_id" widget="many2one_avatar_employee"/>
+                            </div>
+                        </t>
+                    </templates>
+                </kanban>`,
+        });
+
+        assert.strictEqual(kanban.$('.o_kanban_record').text().trim(), '');
+        assert.containsN(kanban, '.o_m2o_avatar', 4);
+        assert.strictEqual(kanban.$('.o_m2o_avatar:nth(0)').data('src'), '/web/image/hr.employee/11/image_128');
+        assert.strictEqual(kanban.$('.o_m2o_avatar:nth(1)').data('src'), '/web/image/hr.employee/7/image_128');
+        assert.strictEqual(kanban.$('.o_m2o_avatar:nth(2)').data('src'), '/web/image/hr.employee/11/image_128');
+        assert.strictEqual(kanban.$('.o_m2o_avatar:nth(3)').data('src'), '/web/image/hr.employee/23/image_128');
+
+        kanban.destroy();
+    });
+
+    QUnit.test('many2one_avatar_employee: click on an employee not associated with a user', async function (assert) {
+        assert.expect(5);
+
+        this.data['hr.employee'].records[0].user_partner_id = false;
+        const form = await createView({
+            View: FormView,
+            model: 'foo',
+            data: this.data,
+            arch: '<form><field name="employee_id" widget="many2one_avatar_employee"/></form>',
+            mockRPC(route, args) {
+                if (args.method === 'read') {
+                    assert.step(`read ${args.model} ${args.args[0]}`);
+                }
+                return this._super(...arguments);
+            },
+            res_id: 1,
+        });
+
+        mock.intercept(form, 'call_service', (ev) => {
+            if (ev.data.service === 'mail_service') {
+                throw new Error('should not call mail_service');
+            }
+            if (ev.data.service === 'notification') {
+                assert.step(`display notification "${ev.data.args[0].title}"`);
+            }
+        }, true);
+
+        assert.strictEqual(form.$('.o_field_widget[name=employee_id]').text().trim(), 'Mario');
+
+        await dom.click(form.$('.o_m2o_avatar'));
+
+        assert.verifySteps([
+            'read foo 1',
+            'read hr.employee 11',
+            'display notification "No user to chat with"',
+        ]);
+
+        form.destroy();
+    });
+
+    QUnit.test('many2one_avatar_employee: click on self', async function (assert) {
+        assert.expect(5);
+
+        const form = await createView({
+            View: FormView,
+            model: 'foo',
+            data: this.data,
+            arch: '<form><field name="employee_id" widget="many2one_avatar_employee"/></form>',
+            mockRPC(route, args) {
+                if (args.method === 'read') {
+                    assert.step(`read ${args.model} ${args.args[0]}`);
+                }
+                return this._super(...arguments);
+            },
+            session: {
+                partner_id: 1,
+            },
+            res_id: 1,
+        });
+
+        mock.intercept(form, 'call_service', (ev) => {
+            if (ev.data.service === 'mail_service') {
+                throw new Error('should not call mail_service');
+            }
+            if (ev.data.service === 'notification') {
+                assert.step(`display notification "${ev.data.args[0].title}"`);
+            }
+        }, true);
+
+        assert.strictEqual(form.$('.o_field_widget[name=employee_id]').text().trim(), 'Mario');
+
+        await dom.click(form.$('.o_m2o_avatar'));
+
+        assert.verifySteps([
+            'read foo 1',
+            'read hr.employee 11',
+            'display notification "Cannot chat with yourself"',
+        ]);
+
+        form.destroy();
+    });
+});
+});
diff --git a/addons/hr/views/hr_templates.xml b/addons/hr/views/hr_templates.xml
index 8197751d378f5a6a2b76d12df585200e2421cefc..2cfac7e0cc809f5fff854e8205a6bd7c235d0f47 100644
--- a/addons/hr/views/hr_templates.xml
+++ b/addons/hr/views/hr_templates.xml
@@ -6,6 +6,13 @@
             <link rel="stylesheet" type="text/scss" href="/hr/static/src/scss/hr.scss"/>
             <script type="text/javascript" src="/hr/static/src/js/chat.js"></script>
             <script type="text/javascript" src="/hr/static/src/js/language.js"></script>
+            <script type="text/javascript" src="/hr/static/src/js/many2one_avatar_employee.js"></script>
+        </xpath>
+    </template>
+
+    <template id="qunit_suite" name="hr tests" inherit_id="web.qunit_suite_tests">
+        <xpath expr="." position="inside">
+            <script type="text/javascript" src="/hr/static/tests/many2one_avatar_employee_tests.js"></script>
         </xpath>
     </template>
 </odoo>
diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst
index e7f350ee01c9a1067ce9985a86be7e2c449e63a3..9a78eeb830a00757889efd83069374c41c0c19b4 100644
--- a/doc/reference/javascript_reference.rst
+++ b/doc/reference/javascript_reference.rst
@@ -2155,6 +2155,11 @@ Relational fields
 
     - Supported field types: *many2one* (pointing to 'res.users')
 
+- many2one_avatar_employee (Many2OneAvatarEmployee)
+    Same as Many2OneAvatarUser, but for many2one fields pointing to 'hr.employee'.
+
+    - Supported field types: *many2one* (pointing to 'hr.employee')
+
 - kanban.many2one (KanbanFieldMany2One)
     Default widget for many2one fields (in kanban view). We need to disable all
     editing in kanban views.