Odoo Theme-Entwicklungshandbuch: Erstellen Sie benutzerdefinierte Themes für Odoo 17/18
Das Odoo-Theme-Ökosystem erwirtschaftet im Odoo App Store jährlich über 12 Millionen US-Dollar, dennoch erhalten weniger als 8 % der Themes eine 5-Sterne-Bewertung. Der Unterschied zwischen einem unvergesslichen Thema und einem Bestseller liegt im Verständnis der Rendering-Pipeline von Odoo, der Vererbung von QWeb-Vorlagen und der Drag-and-Drop-Architektur des Website-Builders.
Dieser Leitfaden behandelt alle Ebenen der Odoo-Theme-Entwicklung – vom Projektgerüst bis zur erweiterten SCSS-Architektur, von der Erstellung benutzerdefinierter Snippets bis hin zur nahtlosen Zusammenarbeit Ihres Themes mit dem Website-Builder von Odoo.
Wichtige Erkenntnisse
- Odoo-Themes sind Standardmodule mit einer spezifischen Verzeichnisstruktur – sie erweitern das
website-Modul und registrieren Vorlagen, SCSS und JavaScript über das Asset-Bündelungssystem. - QWeb ist die serverseitige Template-Engine von Odoo – sie verwendet XML-Anweisungen (t-if, t-foreach, t-call, t-inherit), die in HTML kompiliert werden. Das Verständnis der Vorlagenvererbung mit XPath ist für die Anpassung jedes Teils der Website unerlässlich.
- Die SCSS-Pipeline wird über den Asset Manager von Odoo kompiliert – benutzerdefinierte Variablen überschreiben die Bootstrap 5-Standardeinstellungen und
_variables.scssmuss vor dem Framework geladen werden. - Die Integration des Website-Builders erfordert die Definition von Snippet-Optionen, benutzerdefinierten Bausteinen und dynamischen Inhaltsblöcken, die technisch nicht versierte Benutzer über den Drag-and-Drop-Editor konfigurieren können.
- Responsives Design in Odoo nutzt das Grid-System von Bootstrap 5 mit Odoo-spezifischen Dienstprogrammen – testen Sie alle Haltepunkte und berücksichtigen Sie RTL-Sprachen für internationale Bereitstellungen.
1. Struktur des Themenmoduls
Ein Odoo-Theme folgt der Standardmodulstruktur mit websitespezifischen Ergänzungen:
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
Das Themenmanifest
# __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,
}
Kritischer Hinweis zum Schlüssel assets: In Odoo 17+ ist das Wörterbuch assets in __manifest__.py die bevorzugte Methode zum Registrieren statischer Dateien. Der ältere views/assets.xml-Ansatz mit <template inherit_id="web.assets_frontend"> funktioniert immer noch, wird jedoch auslaufen. Für maximale Kompatibilität verwenden Sie beide.
2. QWeb Templating Deep Dive
QWeb ist die XML-basierte Template-Engine von Odoo. Es verarbeitet Anweisungen mit dem Präfix t- und gibt HTML aus. Das Verständnis von QWeb ist für die Themenentwicklung nicht verhandelbar.
Kernrichtlinien
<!-- 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>
Vorlagenvererbung mit XPath
Hier wird die Themenentwicklung wirkungsvoll. Sie können jede vorhandene Vorlage ändern, ohne sie zu ersetzen:
<!-- 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>
XPath-Positionsreferenz
| Position | Wirkung |
|---|---|
replace | Ersetzen Sie das übereinstimmende Element vollständig |
inside | Innerhalb des übereinstimmenden Elements |
before | Vor dem übereinstimmenden Element |
after | Nach dem übereinstimmenden Element |
attributes | Attribute des übereinstimmenden Elements ändern |
<!-- 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. SCSS-Architektur
Odoos SCSS-Pipeline wird über seinen Asset-Manager kompiliert, was bedeutet, dass Ihre Variablenüberschreibungen vor den Bootstraps-Standardwerten geladen werden müssen. Verwenden Sie die „prepend“-Direktive im Assets-Wörterbuch Ihres Manifests, um sicherzustellen, dass Ihre Datei _variables.scss Priorität hat. Diese einzelne Datei steuert Farben, Schriftarten, Abstände, Rahmenradius und alle anderen Bootstrap-Design-Token in Ihrem Theme.
Variablenüberschreibungen (_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;
Haupt-Stylesheet (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;
}
}
Benutzerdefiniertes Laden von Schriftarten
// _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. Benutzerdefinierte Snippets (Bausteine)
Snippets sind die Drag-and-Drop-Bausteine im Website-Builder von Odoo. Das Erstellen benutzerdefinierter Snippets macht ein Theme wirklich wertvoll.
Definieren einer Snippet-Vorlage
<!-- 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>
Snippet-Optionen (Benutzeranpassung)
<!-- 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>
Snippet JavaScript (Animationen)
// 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. Best Practices für Responsive Design
Mobile-First SCSS
// _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;
}
}
RTL-Unterstützung
// _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. Leistungsoptimierung
Bildoptimierung
<!-- 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"/>
Kritisches CSS-Inlining
<!-- 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>
Strategie zum Laden von Assets
# __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. Website-Builder-Integration
Dynamische Inhaltsblöcke
<!-- 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>
Seitenvorlagen
<!-- 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. Testen Sie Ihr Theme
# 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. Veröffentlichung im Odoo App Store
| Anforderung | Einzelheiten |
|---|---|
| Screenshots | 3-5 hochwertige Screenshots (1920x1080) |
| Symbol | 128x128 PNG im static/description/ |
| Beschreibung | HTML-Beschreibung in static/description/index.html |
| Lizenz | LGPL-3 für Themen (Standardpraxis) |
| Version | Match Odoo-Serie (18.0.x.y.z) |
| Testen | Bestehen Sie die automatisierten Flusenprüfungen von Odoo |
| Demodaten | Fügen Sie Demodaten für „Testen vor dem Kauf“ hinzu |
Häufig gestellte Fragen
Kann ich Tailwind CSS in einem Odoo-Theme verwenden?
Obwohl technisch möglich, führt die Verwendung von Tailwind CSS zusammen mit Odoos integriertem Bootstrap 5 zu Konflikten und erhöht die Bundle-Größe erheblich. Der empfohlene Ansatz ist die Verwendung von Bootstrap 5-Dienstprogrammen und benutzerdefinierten SCSS-Eigenschaften, die sich nativ in die Asset-Pipeline und den Website-Builder von Odoo integrieren lassen. Wenn Sie Dienstprogrammklassen benötigen, die Bootstrap nicht bereitstellt, erstellen Sie stattdessen benutzerdefinierte SCSS-Dienstprogramme.
Wie mache ich mein Theme mit Odoo 17 und 18 kompatibel?
Erstellen Sie separate Zweige für jede Odoo-Version. Odoo 17 verwendet Bootstrap 5.1, während Odoo 18 Bootstrap 5.3 mit CSS-Variablenunterstützung verwendet. Auch die Asset-Registrierung hat sich geändert – Odoo 18 bevorzugt den Manifest-Asset-Schlüssel, während Odoo 17 die XML-Vorlagenvererbung verwendet. Pflegen Sie gemeinsam genutzte SCSS-Teildateien und versionenspezifische Integrationsdateien.
Warum funktionieren meine SCSS-Variablenüberschreibungen nicht?
Die häufigste Ursache ist die Ladereihenfolge. Ihre _variables.scss muss geladen werden, bevor Bootstrap kompiliert wird. Verwenden Sie die „prepend“-Direktive in Ihrem Manifest: („prepend“, „theme_name/static/src/scss/_variables.scss“). Stellen Sie außerdem sicher, dass Sie die richtigen Variablennamen überschreiben – Odoo umschließt einige Bootstrap-Variablen mit Versionen mit o-Präfix wie $o-brand-primary.
Wie funktionieren benutzerdefinierte Snippets mit dem Website-Builder?
Benutzerdefinierte Snippets sind QWeb-Vorlagen, die im Snippet-Panel durch Vorlagenvererbung von website.snippets registriert werden. Jedes Snippet benötigt eine Vorlage für seine HTML-Struktur, optionale Optionen, die in website.snippet_options für das Anpassungsfenster definiert sind, und optionales JavaScript zur Erweiterung von publicWidget.Widget für Interaktivität. Der Website-Builder übernimmt automatisch Drag-and-Drop, Inline-Bearbeitung und Rückgängigmachen/Wiederholen für registrierte Snippets.
Wie fügt man einem Odoo-Theme am besten benutzerdefinierte Schriftarten hinzu?
Platzieren Sie WOFF2-Schriftartendateien in static/src/fonts/ und definieren Sie @font-face-Regeln in Ihrem SCSS. Verwenden Sie „font-display: swap“ für Leistung. Überschreiben Sie dann die Bootstrap-Variable $font-family-sans-serif in Ihrer _variables.scss. Vermeiden Sie das Laden von Schriftarten von externen CDNs, da dadurch eine DNS-Suche hinzugefügt wird und in eingeschränkten Netzwerken möglicherweise ein Fehler auftritt. Unterteilen Sie die Schriftarten so, dass nur die benötigten Zeichenbereiche enthalten sind.
Wie unterstütze ich den Dunkelmodus in meinem Odoo-Theme?
Odoo 18 führte die native Unterstützung des Dunkelmodus durch benutzerdefinierte CSS-Eigenschaften ein. Überschreiben Sie die Dunkelmodusvariablen in Ihrem Theme mit dem Selektor [data-bs-theme="dark"]. Definieren Sie sowohl helle als auch dunkle Varianten Ihrer benutzerdefinierten Farben, Hintergründe und Schatten. Der Website-Builder-Schalter übernimmt den Wechsel – Ihr Theme muss lediglich die richtigen CSS-Variablenwerte für beide Modi bereitstellen.
Nächste Schritte
Bei der Themenentwicklung trifft Design auf Technik. Ein gut aufgebautes Odoo-Theme kann Hunderte von Kunden bedienen und im Odoo App Store erhebliche wiederkehrende Einnahmen generieren.
Für verwandte Anleitungen:
- Odoo Python-Entwicklungshandbuch – Grundlagen der Backend-Entwicklung
- Odoo Custom Module Development – Übersicht über die Modularchitektur
- Odoo REST API Tutorial – API-Integrationsmuster
Benötigen Sie ein individuelles Odoo-Theme oder ein Website-Redesign? Das Odoo-Anpassungsteam von ECOSIRE entwirft und erstellt unternehmenstaugliche Themes mit vollständiger Website-Builder-Integration, responsivem Design über alle Haltepunkte hinweg und RTL-Unterstützung für internationale Bereitstellungen. Wir haben maßgeschneiderte Themes für Kunden in über 40 Ländern bereitgestellt. Kontaktieren Sie uns für eine kostenlose Designberatung.
Geschrieben von
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.
Verwandte Artikel
KI-gestützte Kundensegmentierung: Von RFM zum Predictive Clustering
Erfahren Sie, wie KI die Kundensegmentierung von statischer RFM-Analyse in dynamisches prädiktives Clustering umwandelt. Implementierungsleitfaden mit Python, Odoo und echten ROI-Daten.
KI zur Supply-Chain-Optimierung: Sichtbarkeit, Vorhersage und Automatisierung
Transformieren Sie Lieferkettenabläufe mit KI: Bedarfserkennung, Lieferantenrisikobewertung, Routenoptimierung, Lagerautomatisierung und Störungsvorhersage. Leitfaden 2026.
B2B-E-Commerce-Strategie: Bauen Sie im Jahr 2026 ein Online-Großhandelsgeschäft auf
Meistern Sie den B2B-E-Commerce mit Strategien für Großhandelspreise, Kontoverwaltung, Kreditbedingungen, Punchout-Kataloge und die Konfiguration des Odoo B2B-Portals.