Guide de développement de thèmes Odoo : créez des thèmes personnalisés pour Odoo 17/18
L'écosystème de thèmes Odoo génère plus de 12 millions de dollars par an sur l'App Store Odoo, mais moins de 8 % des thèmes reçoivent une note de 5 étoiles. La différence entre un thème oubliable et un best-seller réside dans la compréhension du pipeline de rendu d'Odoo, de l'héritage des modèles QWeb et de l'architecture glisser-déposer du constructeur de sites Web.
Ce guide couvre toutes les couches du développement de thèmes Odoo : de l'échafaudage de projet à l'architecture SCSS avancée, de la création d'extraits personnalisés à l'utilisation transparente de votre thème avec le créateur de site Web d'Odoo.
Points clés à retenir
- Les thèmes Odoo sont des modules standards avec une structure de répertoires spécifique — ils étendent le module
websiteet enregistrent les modèles, SCSS et JavaScript via le système de regroupement d'actifs. - QWeb est le moteur de modèles côté serveur d'Odoo — il utilise des directives XML (t-if, t-foreach, t-call, t-inherit) qui se compilent en HTML. Comprendre l'héritage des modèles avec XPath est essentiel pour personnaliser n'importe quelle partie du site Web.
- Le pipeline SCSS se compile via le gestionnaire d'actifs d'Odoo — les variables personnalisées remplacent les valeurs par défaut de Bootstrap 5 et
_variables.scssdoit être chargé avant le framework. - L'intégration du créateur de sites Web nécessite la définition d'options d'extraits de code, de blocs de construction personnalisés et de blocs de contenu dynamique que les utilisateurs non techniques peuvent configurer via l'éditeur glisser-déposer. - La conception réactive dans Odoo exploite le système de grille de Bootstrap 5 avec des utilitaires spécifiques à Odoo : testez sur tous les points d'arrêt et envisagez les langages RTL pour les déploiements internationaux.
1. Structure du module thématique
Un thème Odoo suit la structure de module standard avec des ajouts spécifiques au site :
theme_ecosire/
├── __init__.py
├── __manifest__.py
├── data/
│ ├── theme_data.xml # Default pages, menus
│ └── images.xml # Pre-loaded image library
├── static/
│ ├── description/
│ │ ├── icon.png # Module icon (128x128)
│ │ └── icon.svg # SVG version
│ ├── src/
│ │ ├── img/ # Theme images
│ │ ├── fonts/ # Custom fonts
│ │ ├── scss/
│ │ │ ├── _variables.scss # Bootstrap variable overrides
│ │ │ ├── _mixins.scss # Custom SCSS mixins
│ │ │ ├── _typography.scss # Font stacks, sizes
│ │ │ ├── _header.scss # Header styles
│ │ │ ├── _footer.scss # Footer styles
│ │ │ ├── _snippets.scss # Snippet-specific styles
│ │ │ └── primary.scss # Main entry point
│ │ ├── js/
│ │ │ ├── snippets/ # Snippet JS (animations, interactions)
│ │ │ └── theme.js # Theme-wide JS
│ │ └── xml/
│ │ └── snippets.xml # OWL snippet templates
│ └── img/ # Public images (backgrounds, icons)
├── views/
│ ├── assets.xml # Asset bundle registration
│ ├── layout.xml # Header/footer templates
│ ├── pages.xml # Pre-built page templates
│ └── snippets/
│ ├── options.xml # Snippet options (color pickers, etc.)
│ └── s_custom_block.xml # Custom snippet definitions
└── tests/
└── test_theme.py # Theme installation tests
Le manifeste du thème
# __manifest__.py
{
'name': 'Theme Ecosire',
'description': 'Modern, performance-optimized theme for Odoo Website',
'category': 'Theme',
'version': '18.0.1.0.0',
'author': 'ECOSIRE Private Limited',
'website': 'https://ecosire.com',
'license': 'LGPL-3',
'depends': ['website'],
'data': [
'views/assets.xml',
'views/layout.xml',
'views/pages.xml',
'views/snippets/options.xml',
'views/snippets/s_custom_block.xml',
],
'assets': {
'web.assets_frontend': [
# Variables MUST come before Bootstrap
('prepend', 'theme_ecosire/static/src/scss/_variables.scss'),
'theme_ecosire/static/src/scss/primary.scss',
'theme_ecosire/static/src/js/theme.js',
],
'website.assets_wysiwyg': [
'theme_ecosire/static/src/js/snippets/*.js',
],
},
'images': [
'static/description/banner.png',
'static/description/theme_screenshot.jpg',
],
'snippet_lists': {
'website.snippets': [
('replace', 'theme_ecosire.s_custom_hero'),
],
},
'installable': True,
'application': False,
'auto_install': False,
}
Remarque critique concernant la clé assets : Dans Odoo 17+, le dictionnaire assets dans __manifest__.py est le moyen préféré pour enregistrer des fichiers statiques. L'ancienne approche views/assets.xml utilisant <template inherit_id="web.assets_frontend"> fonctionne toujours mais est en cours de suppression. Utilisez les deux pour une compatibilité maximale.
2. Analyse approfondie des modèles QWeb
QWeb est le moteur de modèles basé sur XML d'Odoo. Il traite les directives préfixées par t- et génère du HTML. Comprendre QWeb n'est pas négociable pour le développement de thèmes.
Directives fondamentales
<!-- Conditional rendering -->
<div t-if="website.is_publisher()">
<span>Edit mode controls here</span>
</div>
<div t-elif="request.env.user._is_internal()">
<span>Internal user view</span>
</div>
<div t-else="">
<span>Public visitor view</span>
</div>
<!-- Loops -->
<ul>
<t t-foreach="website.menu_id.child_id" t-as="menu_item">
<li t-attf-class="nav-item #{('active' if menu_item.is_active else '')}">
<a t-att-href="menu_item.url" t-out="menu_item.name"/>
</li>
</t>
</ul>
<!-- Variable setting -->
<t t-set="company" t-value="res_company"/>
<t t-set="primary_color" t-value="'#FF6B00'"/>
<!-- Output (auto-escaped) -->
<span t-out="partner.name"/>
<!-- Raw HTML output (use with caution) -->
<div t-raw="sanitized_html_content"/>
<!-- Dynamic attributes -->
<div t-att-id="'section-' + str(section.id)"
t-attf-class="container #{extra_class}"
t-att-data-section="section.sequence"/>
<!-- Template calling -->
<t t-call="theme_ecosire.hero_section">
<t t-set="title">Welcome to Our Store</t>
<t t-set="subtitle">Premium Odoo Themes</t>
</t>
Héritage de modèles avec XPath
C’est là que le développement de thèmes devient puissant. Vous pouvez modifier n'importe quel modèle existant sans le remplacer :
<!-- views/layout.xml -->
<template id="custom_header" inherit_id="website.layout" name="Custom Header">
<!-- Replace the entire header -->
<xpath expr="//header" position="replace">
<header id="top" class="ecosire-header">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" t-att-href="'/'">
<img t-if="website.logo"
t-att-src="website.image_url(website, 'logo')"
alt="Logo" class="img-fluid" style="max-height: 50px;"/>
</a>
<!-- Custom mega menu -->
<t t-call="theme_ecosire.mega_menu"/>
<!-- CTA button -->
<a href="/contactus" class="btn btn-primary ms-3">
Get Started
</a>
</div>
</nav>
</header>
</xpath>
<!-- Add a top bar before the header -->
<xpath expr="//header[@id='top']" position="before">
<div class="ecosire-topbar bg-dark text-white py-1">
<div class="container d-flex justify-content-between">
<span t-out="res_company.phone"/>
<span t-out="res_company.email"/>
</div>
</div>
</xpath>
<!-- Modify footer: add a newsletter section -->
<xpath expr="//footer" position="inside">
<div class="ecosire-newsletter bg-primary text-white py-5">
<div class="container text-center">
<h3>Stay Updated</h3>
<t t-call="website.s_newsletter_block"/>
</div>
</div>
</xpath>
</template>
Référence de position XPath
| Poste | Effet |
|---|---|
replace | Remplacer entièrement l'élément correspondant |
inside | Ajouter à l'intérieur de l'élément correspondant |
before | Insérer avant l'élément correspondant |
after | Insérer après l'élément correspondant |
attributes | Modifier les attributs de l'élément correspondant |
<!-- Modify attributes example -->
<xpath expr="//div[@id='wrapwrap']" position="attributes">
<attribute name="class" add="ecosire-theme" separator=" "/>
</xpath>
<!-- Remove an element -->
<xpath expr="//div[hasclass('o_not_editable')]" position="replace"/>
3. Architecture SCSS
Le pipeline SCSS d'Odoo est compilé via son gestionnaire d'actifs, ce qui signifie que vos remplacements de variables doivent être chargés avant les valeurs par défaut de Bootstrap. Utilisez la directive « prepend » dans le dictionnaire des actifs de votre manifeste pour garantir que votre fichier _variables.scss est prioritaire. Ce fichier unique contrôle les couleurs, les polices, l'espacement, le rayon de bordure et tous les autres jetons de conception Bootstrap de votre thème.
Remplacements de variables (_variables.scss)
// _variables.scss — loaded BEFORE Bootstrap
// Override Bootstrap 5 defaults
// Brand Colors
$o-brand-primary: #FF6B00;
$o-brand-secondary: #1A1F36;
// Bootstrap color overrides
$primary: $o-brand-primary;
$secondary: $o-brand-secondary;
$success: #00C853;
$info: #2196F3;
$warning: #FFB300;
$danger: #FF1744;
// Typography
$font-family-sans-serif: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-family-monospace: 'JetBrains Mono', 'Fira Code', monospace;
$font-size-base: 1rem;
$headings-font-weight: 700;
$headings-line-height: 1.2;
// Spacing
$spacer: 1rem;
// Border radius
$border-radius: 0.5rem;
$border-radius-lg: 1rem;
$border-radius-pill: 50rem;
// Shadows
$box-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
$box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
$box-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
// Navbar
$navbar-padding-y: 1rem;
$navbar-brand-font-size: 1.5rem;
// Cards
$card-border-radius: $border-radius-lg;
$card-border-color: transparent;
$card-box-shadow: $box-shadow;
Feuille de style principale (primary.scss)
// primary.scss — Main entry point
@import 'variables'; // Already prepended, but import for IDE support
@import 'mixins';
@import 'typography';
@import 'header';
@import 'footer';
@import 'snippets';
// ============================================
// Global Overrides
// ============================================
#wrapwrap.ecosire-theme {
// Smooth scrolling
scroll-behavior: smooth;
// Better text rendering
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// ============================================
// Section Spacing
// ============================================
.s_section {
padding: 5rem 0;
@media (max-width: 768px) {
padding: 3rem 0;
}
}
// ============================================
// Button Styles
// ============================================
.btn-primary {
background: linear-gradient(135deg, $primary, darken($primary, 10%));
border: none;
padding: 0.75rem 2rem;
font-weight: 600;
letter-spacing: 0.02em;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba($primary, 0.4);
}
}
// ============================================
// Card Component
// ============================================
.ecosire-card {
background: white;
border-radius: $border-radius-lg;
overflow: hidden;
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: $box-shadow-lg;
}
&__image {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
&__body {
padding: 1.5rem;
}
}
Chargement de polices personnalisées
// _typography.scss
@font-face {
font-family: 'Inter';
src: url('/theme_ecosire/static/src/fonts/Inter-Variable.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('/theme_ecosire/static/src/fonts/Inter-Variable-Italic.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: italic;
}
// Heading hierarchy
h1, .h1 { font-size: 2.5rem; font-weight: 800; }
h2, .h2 { font-size: 2rem; font-weight: 700; }
h3, .h3 { font-size: 1.5rem; font-weight: 600; }
h4, .h4 { font-size: 1.25rem; font-weight: 600; }
4. Extraits personnalisés (blocs de construction)
Les extraits de code sont les éléments de base du glisser-déposer dans le créateur de site Web d'Odoo. La création d’extraits personnalisés est ce qui rend un thème vraiment précieux.
Définir un modèle d'extrait de code
<!-- views/snippets/s_custom_block.xml -->
<odoo>
<!-- The snippet template -->
<template id="s_ecosire_hero" name="Ecosire Hero Section">
<section class="s_ecosire_hero pt-5 pb-5 o_cc o_cc1">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h1 class="display-4 fw-bold">
Transform Your Business
</h1>
<p class="lead text-muted mt-3">
Enterprise solutions that scale with your ambitions.
</p>
<div class="mt-4">
<a href="/contactus" class="btn btn-primary btn-lg me-3">
Get Started
</a>
<a href="/about" class="btn btn-outline-secondary btn-lg">
Learn More
</a>
</div>
</div>
<div class="col-lg-6">
<img src="/theme_ecosire/static/src/img/hero-illustration.svg"
alt="Hero" class="img-fluid" loading="lazy"/>
</div>
</div>
</div>
</section>
</template>
<!-- Register snippet in the builder panel -->
<template id="snippets" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_structure']//t[@t-snippet]/.."
position="inside">
<t t-snippet="theme_ecosire.s_ecosire_hero"
t-thumbnail="/theme_ecosire/static/src/img/snippets/hero_thumb.png"/>
</xpath>
</template>
</odoo>
Options d'extrait de code (personnalisation utilisateur)
<!-- views/snippets/options.xml -->
<odoo>
<template id="s_ecosire_hero_options" inherit_id="website.snippet_options">
<xpath expr="." position="inside">
<div data-js="EcosireHeroOptions"
data-selector=".s_ecosire_hero">
<!-- Layout selector -->
<we-select string="Layout">
<we-button data-select-class="s_hero_left">Text Left</we-button>
<we-button data-select-class="s_hero_center">Centered</we-button>
<we-button data-select-class="s_hero_right">Text Right</we-button>
</we-select>
<!-- Background style -->
<we-select string="Background">
<we-button data-select-class="bg-white">White</we-button>
<we-button data-select-class="bg-dark text-white">Dark</we-button>
<we-button data-select-class="bg-gradient">Gradient</we-button>
</we-select>
<!-- Animation toggle -->
<we-checkbox string="Enable Animation"
data-toggle-class="s_hero_animated"/>
</div>
</xpath>
</template>
</odoo>
Extrait JavaScript (Animations)
// static/src/js/snippets/s_ecosire_hero.js
/** @odoo-module */
import publicWidget from '@web/legacy/js/public/public_widget';
const EcosireHero = publicWidget.Widget.extend({
selector: '.s_ecosire_hero.s_hero_animated',
disabledInEditableMode: true,
start() {
this._super(...arguments);
this._initAnimation();
return Promise.resolve();
},
_initAnimation() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.2 }
);
this.el.querySelectorAll('.animate-target').forEach(el => {
observer.observe(el);
});
},
destroy() {
this._super(...arguments);
},
});
publicWidget.registry.EcosireHero = EcosireHero;
export default EcosireHero;
5. Meilleures pratiques de conception réactive
SCSS axé sur le mobile
// _responsive.scss
// Mobile-first breakpoints (Bootstrap 5)
// xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px
.ecosire-header {
// Mobile default
padding: 0.5rem 0;
.navbar-brand img {
max-height: 35px;
}
.nav-link {
padding: 0.75rem 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
// Tablet and up
@include media-breakpoint-up(md) {
padding: 0.75rem 0;
.nav-link {
padding: 0.5rem 1rem;
border-bottom: none;
}
}
// Desktop
@include media-breakpoint-up(lg) {
padding: 1rem 0;
.navbar-brand img {
max-height: 50px;
}
}
}
// Touch-friendly elements
@media (hover: none) {
.btn {
min-height: 44px;
min-width: 44px;
}
.nav-link {
min-height: 44px;
display: flex;
align-items: center;
}
}
Prise en charge RTL
// _rtl.scss — Critical for Arabic, Hebrew, Urdu markets
html[dir="rtl"] {
.ecosire-header {
.navbar-brand {
margin-left: 1rem;
margin-right: 0;
}
.btn + .btn {
margin-right: 0.75rem;
margin-left: 0;
}
}
.ecosire-card__body {
text-align: right;
}
// Use logical properties where possible
.ms-3 { margin-inline-start: 1rem; }
.me-3 { margin-inline-end: 1rem; }
}
6. Optimisation des performances
Optimisation des images
<!-- Use responsive images with srcset -->
<img t-att-src="'/web/image/ir.attachment/%s/datas/800x400' % image.id"
t-att-srcset="'/web/image/ir.attachment/%s/datas/400x200 400w, /web/image/ir.attachment/%s/datas/800x400 800w, /web/image/ir.attachment/%s/datas/1200x600 1200w' % (image.id, image.id, image.id)"
sizes="(max-width: 576px) 400px, (max-width: 992px) 800px, 1200px"
loading="lazy"
decoding="async"
alt="Descriptive alt text"/>
Inline CSS critique
<!-- views/layout.xml -->
<template id="critical_css" inherit_id="website.layout">
<xpath expr="//head" position="inside">
<style type="text/css">
/* Inline critical above-the-fold CSS */
.ecosire-header { min-height: 72px; }
.s_ecosire_hero { min-height: 60vh; }
.navbar-brand img { max-height: 50px; }
</style>
</xpath>
</template>
Stratégie de chargement des actifs
# __manifest__.py
'assets': {
# Critical CSS — loaded in head
'web.assets_frontend': [
('prepend', 'theme_ecosire/static/src/scss/_variables.scss'),
'theme_ecosire/static/src/scss/primary.scss',
],
# Non-critical JS — deferred
'web.assets_frontend_lazy': [
'theme_ecosire/static/src/js/animations.js',
'theme_ecosire/static/src/js/parallax.js',
],
# Editor-only assets
'website.assets_wysiwyg': [
'theme_ecosire/static/src/js/snippets/*.js',
'theme_ecosire/static/src/scss/_editor.scss',
],
},
7. Intégration du créateur de site Web
Blocs de contenu dynamique
<!-- A snippet that fetches products dynamically -->
<template id="s_featured_products" name="Featured Products Grid">
<section class="s_featured_products pt-5 pb-5">
<div class="container">
<h2 class="text-center mb-5">Featured Products</h2>
<div class="row" t-if="products">
<t t-foreach="products[:8]" t-as="product">
<div class="col-md-3 mb-4">
<div class="ecosire-card h-100">
<img t-att-src="product.image_url"
t-att-alt="product.name"
class="ecosire-card__image" loading="lazy"/>
<div class="ecosire-card__body">
<h5 t-out="product.name"/>
<p class="text-primary fw-bold"
t-out="product.list_price"
t-options='{"widget": "monetary"}'/>
<a t-att-href="product.website_url"
class="btn btn-outline-primary btn-sm">
View Details
</a>
</div>
</div>
</div>
</t>
</div>
</div>
</section>
</template>
Modèles de pages
<!-- Pre-built page templates that users can select -->
<template id="landing_page_template" name="Landing Page">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<t t-call="theme_ecosire.s_ecosire_hero"/>
<t t-call="theme_ecosire.s_features_grid"/>
<t t-call="theme_ecosire.s_testimonials"/>
<t t-call="theme_ecosire.s_cta_banner"/>
</div>
</t>
</template>
8. Tester votre thème
# tests/test_theme.py
from odoo.tests.common import HttpCase, tagged
@tagged('post_install', '-at_install')
class TestThemeEcosire(HttpCase):
def test_01_theme_installation(self):
"""Theme installs without errors."""
module = self.env['ir.module.module'].search([
('name', '=', 'theme_ecosire')
])
self.assertEqual(module.state, 'installed')
def test_02_homepage_loads(self):
"""Homepage renders with theme applied."""
response = self.url_open('/')
self.assertEqual(response.status_code, 200)
self.assertIn('ecosire-theme', response.text)
def test_03_snippets_available(self):
"""Custom snippets appear in the builder."""
self.start_tour('/web#action=website.action_website',
'theme_ecosire_snippet_tour', login='admin')
def test_04_responsive_meta(self):
"""Viewport meta tag is present."""
response = self.url_open('/')
self.assertIn('viewport', response.text)
9. Publication sur l'App Store Odoo
| Exigence | Détails |
|---|---|
| Captures d'écran | 3 à 5 captures d'écran de haute qualité (1920 x 1080) |
| Icône | 128x128 PNG en static/description/ |
| Descriptif | Description HTML dans static/description/index.html |
| Licence | LGPL-3 pour les thèmes (pratique standard) |
| Version | Série Match Odoo (18.0.x.y.z) |
| Tests | Passez les contrôles automatisés de peluches d'Odoo |
| Données de démonstration | Inclure des données de démonstration pour essayer avant d'acheter |
Questions fréquemment posées
Puis-je utiliser Tailwind CSS dans un thème Odoo ?
Bien que techniquement possible, l'utilisation de Tailwind CSS avec Bootstrap 5 intégré d'Odoo crée des conflits et augmente considérablement la taille du bundle. L'approche recommandée consiste à utiliser les utilitaires Bootstrap 5 et les propriétés personnalisées SCSS, qui s'intègrent nativement au pipeline d'actifs et au constructeur de sites Web d'Odoo. Si vous avez besoin de classes d'utilitaires que Bootstrap ne fournit pas, créez plutôt des utilitaires SCSS personnalisés.
Comment rendre mon thème compatible avec Odoo 17 et 18 ?
Créez des branches distinctes pour chaque version d'Odoo. Odoo 17 utilise Bootstrap 5.1 tandis qu'Odoo 18 utilise Bootstrap 5.3 avec prise en charge des variables CSS. L'enregistrement des actifs a également changé : Odoo 18 préfère la clé des actifs manifestes tandis qu'Odoo 17 utilise l'héritage de modèle XML. Conservez les fichiers partiels SCSS partagés et les fichiers d'intégration spécifiques à la version.
Pourquoi mes remplacements de variables SCSS ne fonctionnent-ils pas ?
La cause la plus courante est l'ordre de chargement. Votre _variables.scss doit être chargé avant la compilation de Bootstrap. Utilisez la directive 'prepend' dans votre manifeste : ('prepend', 'theme_name/static/src/scss/_variables.scss'). Assurez-vous également de remplacer les noms de variables corrects : Odoo enveloppe certaines variables Bootstrap avec des versions préfixées o- comme $o-brand-primary.
Comment les extraits personnalisés fonctionnent-ils avec le créateur de site Web ?
Les extraits personnalisés sont des modèles QWeb enregistrés dans le panneau d'extraits via l'héritage de modèle de website.snippets. Chaque extrait de code nécessite un modèle pour sa structure HTML, des options facultatives définies dans website.snippet_options pour le panneau de personnalisation et une extension JavaScript facultative de publicWidget.Widget pour l'interactivité. Le créateur de site Web gère automatiquement le glisser-déposer, l'édition en ligne et l'annulation/rétablissement des extraits de code enregistrés.
Quelle est la meilleure façon d'ajouter des polices personnalisées à un thème Odoo ?
Placez les fichiers de polices WOFF2 dans static/src/fonts/ et définissez les règles @font-face dans votre SCSS. Utilisez font-display : échangez pour les performances. Remplacez ensuite la variable Bootstrap $font-family-sans-serif dans votre _variables.scss. Évitez de charger des polices à partir de CDN externes, car cela ajoute une recherche DNS et peut échouer sur les réseaux restreints. Sous-ensemble de polices pour inclure uniquement les plages de caractères nécessaires.
Comment puis-je prendre en charge le mode sombre dans mon thème Odoo ?
Odoo 18 a introduit la prise en charge native du mode sombre via les propriétés personnalisées CSS. Remplacez les variables du mode sombre dans votre thème à l'aide du sélecteur [data-bs-theme="dark"]. Définissez les variantes claires et sombres de vos couleurs, arrière-plans et ombres personnalisés. Le bouton du créateur de site Web gère le changement : votre thème doit simplement fournir les bonnes valeurs de variable CSS pour les deux modes.
Prochaines étapes
Le développement de thèmes est le point où la conception rencontre l’ingénierie. Un thème Odoo bien construit peut servir des centaines de clients et générer des revenus récurrents importants sur l'App Store Odoo.
Pour les guides associés :
- Guide de développement Odoo Python — Principes fondamentaux du développement backend
- Développement de modules personnalisés Odoo — Présentation de l'architecture du module
- Tutoriel API REST Odoo — Modèles d'intégration API
Besoin d'un thème Odoo personnalisé ou d'une refonte de site Web ? L'équipe de personnalisation Odoo d'ECOSIRE conçoit et crée des thèmes de niveau entreprise avec une intégration complète du constructeur de sites Web, une conception réactive sur tous les points d'arrêt et une prise en charge RTL pour les déploiements internationaux. Nous avons fourni des thèmes personnalisés à des clients dans plus de 40 pays. Contactez-nous pour une consultation de conception gratuite.
Rédigé par
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
Articles connexes
Segmentation client basée sur l'IA : du RFM au clustering prédictif
Découvrez comment l'IA transforme la segmentation client de l'analyse RFM statique au clustering prédictif dynamique. Guide d'implémentation avec Python, Odoo et données de retour sur investissement réel.
IA pour l'optimisation de la chaîne d'approvisionnement : visibilité, prédiction et automatisation
Transformez les opérations de la chaîne d'approvisionnement grâce à l'IA : détection de la demande, évaluation des risques des fournisseurs, optimisation des itinéraires, automatisation des entrepôts et prévision des perturbations. Guide 2026.
Stratégie de commerce électronique B2B : créer une entreprise de vente en gros en ligne en 2026
Maîtrisez le commerce électronique B2B avec des stratégies de prix de gros, de gestion des comptes, de conditions de crédit, de catalogues punchout et de configuration du portail Odoo B2B.