diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py
index a1be5bc8050..11c287a3e5a 100644
--- a/addons/web/__manifest__.py
+++ b/addons/web/__manifest__.py
@@ -132,7 +132,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/scss/attachment_preview.scss',
'web/static/src/legacy/scss/base_document_layout.scss',
'web/static/src/legacy/scss/special_fields.scss',
- 'web/static/src/legacy/scss/ribbon.scss',
'web/static/src/legacy/scss/base_settings.scss',
'web/static/src/legacy/scss/report_backend.scss',
'web/static/src/legacy/scss/fields_extra.scss',
diff --git a/addons/web/static/src/views/form/form_compiler.js b/addons/web/static/src/views/form/form_compiler.js
index d06acb5a23b..a95ce979535 100644
--- a/addons/web/static/src/views/form/form_compiler.js
+++ b/addons/web/static/src/views/form/form_compiler.js
@@ -190,7 +190,9 @@ export class FormCompiler extends ViewCompiler {
*/
compileForm(el, params) {
const sheetNode = el.querySelector("sheet");
- const displayClasses = sheetNode ? `d-flex {{ uiService.size < ${SIZES.XXL} ? "flex-column" : "flex-nowrap h-100" }}` : "d-block";
+ const displayClasses = sheetNode
+ ? `d-flex {{ uiService.size < ${SIZES.XXL} ? "flex-column" : "flex-nowrap h-100" }}`
+ : "d-block";
const form = createElement("div", {
"t-att-class": "props.class",
"t-attf-class": `{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} ${displayClasses}`,
@@ -521,7 +523,7 @@ export class FormCompiler extends ViewCompiler {
sheetBG.className = "o_form_sheet_bg";
const sheetFG = createElement("div");
- sheetFG.className = "o_form_sheet";
+ sheetFG.className = "o_form_sheet position-relative";
append(sheetBG, sheetFG);
for (const child of el.childNodes) {
diff --git a/addons/web/static/src/views/widgets/ribbon/ribbon.js b/addons/web/static/src/views/widgets/ribbon/ribbon.js
new file mode 100644
index 00000000000..e580feb21ff
--- /dev/null
+++ b/addons/web/static/src/views/widgets/ribbon/ribbon.js
@@ -0,0 +1,52 @@
+/** @odoo-module */
+
+import { registry } from "@web/core/registry";
+import { standardWidgetProps } from "../standard_widget_props";
+
+const { Component } = owl;
+
+/**
+ * This widget adds a ribbon on the top right side of the form
+ *
+ * - You can specify the text with the title prop.
+ * - You can specify the title (tooltip) with the tooltip prop.
+ * - You can specify a background color for the ribbon with the bg_color prop
+ * using bootstrap classes :
+ * (bg-primary, bg-secondary, bg-success, bg-danger, bg-warning, bg-info,
+ * bg-light, bg-dark, bg-white)
+ *
+ * If you don't specify the bg_color prop the bg-success class will be used
+ * by default.
+ */
+class RibbonWidget extends Component {
+ get classes() {
+ let classes = this.props.bgClass;
+ if (this.props.text.length > 15) {
+ classes += " o_small";
+ } else if (this.props.text.length > 10) {
+ classes += " o_medium";
+ }
+ return classes;
+ }
+}
+
+RibbonWidget.template = "web.Ribbon";
+RibbonWidget.props = {
+ ...standardWidgetProps,
+ text: { type: String },
+ title: { type: String, optional: true },
+ bgClass: { type: String, optional: true },
+};
+RibbonWidget.defaultProps = {
+ title: "",
+ bgClass: "bg-success",
+};
+RibbonWidget.extractProps = ({ attrs }) => {
+ return {
+ text: attrs.title || attrs.text,
+ title: attrs.tooltip,
+ bgClass: attrs.bg_color,
+ };
+};
+
+registry.category("view_widgets").add("web_ribbon", RibbonWidget);
diff --git a/addons/web/static/src/legacy/scss/ribbon.scss b/addons/web/static/src/views/widgets/ribbon/ribbon.scss
similarity index 100%
rename from addons/web/static/src/legacy/scss/ribbon.scss
rename to addons/web/static/src/views/widgets/ribbon/ribbon.scss
diff --git a/addons/web/static/src/views/widgets/ribbon/ribbon.xml b/addons/web/static/src/views/widgets/ribbon/ribbon.xml
new file mode 100644
index 00000000000..d1776761120
--- /dev/null
+++ b/addons/web/static/src/views/widgets/ribbon/ribbon.xml
@@ -0,0 +1,12 @@
+
+
+
+