Odoo テーマ開発ガイド: Odoo 17/18 のカスタム テーマを構築する
Odoo テーマのエコシステムは、Odoo App Store で年間 1,200 万ドル以上を生み出していますが、5 つ星の評価を獲得しているテーマは 8% 未満です。忘れられないテーマとベストセラーの違いは、Odoo のレンダリング パイプライン、QWeb テンプレートの継承、および Web サイト ビルダーのドラッグ アンド ドロップ アーキテクチャを理解することによって決まります。
このガイドでは、プロジェクトのスキャフォールディングから高度な SCSS アーキテクチャ、カスタム スニペットの構築から Odoo の Web サイト ビルダーでテーマをシームレスに動作させるまで、Odoo テーマ開発のあらゆる層をカバーしています。
重要なポイント
- Odoo テーマは、特定のディレクトリ構造を持つ標準モジュールです。
websiteモジュールを拡張し、アセット バンドル システムを介してテンプレート、SCSS、および JavaScript を登録します。 - QWeb は Odoo のサーバー側テンプレート エンジンです - HTML にコンパイルする XML ディレクティブ (t-if、t-foreach、t-call、t-inherit) を使用します。 XPath によるテンプレートの継承を理解することは、Web サイトのあらゆる部分をカスタマイズするために不可欠です。
- SCSS パイプライン は Odoo のアセット マネージャーを通じてコンパイルされます。カスタム変数は Bootstrap 5 のデフォルトをオーバーライドし、フレームワークの前に
_variables.scssをロードする必要があります。 - Web サイト ビルダーの統合 では、スニペット オプション、カスタム ビルディング ブロック、および動的コンテンツ ブロックを定義する必要があり、技術者以外のユーザーはドラッグ アンド ドロップ エディターを使用してこれらを構成できます。
- Odoo のレスポンシブ デザイン は、Odoo 固有のユーティリティを備えた Bootstrap 5 のグリッド システムを活用しています。すべてのブレークポイントにわたってテストし、国際展開向けに RTL 言語を検討します。
1. テーマモジュールの構造
Odoo テーマは、Web サイト固有の追加を加えた標準モジュール構造に従います。
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
テーママニフェスト
# __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,
}
assets キーに関する重要な注意事項: Odoo 17 以降では、__manifest__.py の assets 辞書が静的ファイルを登録するための推奨される方法です。 <template inherit_id="web.assets_frontend"> を使用した古い views/assets.xml アプローチはまだ機能しますが、段階的に廃止されています。互換性を最大限に高めるには両方を使用してください。
2. QWeb テンプレートの詳細
QWeb は、Odoo の XML ベースのテンプレート エンジンです。 t- というプレフィックスが付いたディレクティブを処理し、HTML を出力します。テーマ開発においては、QWeb を理解することは譲れません。
コアディレクティブ
<!-- 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>
XPath によるテンプレートの継承
ここでテーマの開発が強力になります。既存のテンプレートを置き換えずに変更できます。
<!-- 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 位置参照
| ポジション | 効果 |
|---|---|
| コード0 | 一致した要素を完全に置き換える |
| コード0 | 一致した要素内に追加 |
| コード0 | 一致した要素の前に挿入 |
| コード0 | 一致した要素の後に挿入 |
| コード0 | 一致した要素の属性を変更する |
<!-- 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 アーキテクチャ
Odoo の SCSS パイプラインはアセット マネージャーを通じてコンパイルされます。つまり、ブートストラップのデフォルトの前に変数オーバーライドをロードする必要があります。マニフェストのアセット ディクショナリで「prepend」ディレクティブを使用して、_variables.scss ファイルが優先されるようにします。この 1 つのファイルは、テーマ内の色、フォント、間隔、境界線の半径、その他すべてのブートストラップ デザイン トークンを制御します。
変数のオーバーライド (_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;
メイン スタイルシート (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;
}
}
カスタム フォントの読み込み
// _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. カスタム スニペット (構成要素)
スニペットは、Odoo の Web サイト ビルダーのドラッグ アンド ドロップの構成要素です。カスタム スニペットを作成することで、テーマが真に価値のあるものになります。
スニペット テンプレートの定義
<!-- 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>
スニペット オプション (ユーザーのカスタマイズ)
<!-- 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>
スニペット JavaScript (アニメーション)
// 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. レスポンシブデザインのベストプラクティス
モバイルファースト 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 サポート
// _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. パフォーマンスの最適化
画像の最適化
<!-- 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"/>
クリティカル CSS インライン化
<!-- 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>
アセットロード戦略
# __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. ウェブサイトビルダーの統合
動的コンテンツブロック
<!-- 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>
ページ テンプレート
<!-- 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. テーマのテスト
# 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. Odoo App Store での公開
| 要件 | 詳細 |
|---|---|
| スクリーンショット | 3 ~ 5 枚の高品質スクリーンショット (1920x1080) |
| アイコン | static/description/ の 128x128 PNG |
| 説明 | static/description/index.html の HTML 記述 |
| ライセンス | LGPL-3 テーマ (標準的な実践) |
| バージョン | Odoo シリーズと一致 (18.0.x.y.z) |
| テスト | Odoo の自動リンティング チェックに合格する |
| デモデータ | 購入前に試すためのデモ データを含める |
よくある質問
Odoo テーマで Tailwind CSS を使用できますか?
技術的には可能ですが、Odoo の組み込み Bootstrap 5 と一緒に Tailwind CSS を使用すると競合が発生し、バンドル サイズが大幅に増加します。推奨されるアプローチは、Odoo のアセット パイプラインおよび Web サイト ビルダーとネイティブに統合される Bootstrap 5 ユーティリティと SCSS カスタム プロパティを使用することです。 Bootstrap が提供しないユーティリティ クラスが必要な場合は、代わりにカスタム SCSS ユーティリティを作成してください。
テーマを Odoo 17 と 18 の両方と互換性を持たせるにはどうすればよいですか?
Odoo バージョンごとに個別のブランチを作成します。 Odoo 17 は Bootstrap 5.1 を使用しますが、Odoo 18 は CSS 変数をサポートする Bootstrap 5.3 を使用します。アセット登録も変更されました。Odoo 18 ではマニフェスト アセット キーが優先されますが、Odoo 17 では XML テンプレートの継承が使用されます。共有 SCSS パーシャルとバージョン固有の統合ファイルを維持します。
SCSS 変数のオーバーライドが機能しないのはなぜですか?
最も一般的な原因はロード順序です。 _variables.scss は、ブートストラップをコンパイルする前にロードする必要があります。マニフェストで 'prepend' ディレクティブを使用します: ('prepend', 'theme_name/static/src/scss/_variables.scss')。また、正しい変数名をオーバーライドしていることを確認してください。Odoo は、$o-brand-primary など、一部のブートストラップ変数を o- プレフィックス付きバージョンでラップします。
カスタム スニペットはウェブサイト ビルダーとどのように連携しますか?
カスタム スニペットは、website.snippets のテンプレート継承を通じてスニペット パネルに登録された QWeb テンプレートです。各スニペットには、HTML 構造用のテンプレート、カスタマイズ パネル用の website.snippet_options で定義されたオプション オプション、および対話機能用に publicWidget.Widget を拡張するオプションの JavaScript が必要です。ウェブサイト ビルダーは、登録されたスニペットのドラッグ アンド ドロップ、インライン編集、元に戻す/やり直しを自動的に処理します。
Odoo テーマにカスタム フォントを追加する最良の方法は何ですか?
WOFF2 フォント ファイルを static/src/fonts/ に配置し、SCSS で @font-face ルールを定義します。パフォーマンスのために font-display: swap を使用します。次に、_variables.scss 内の $font-family-sans-serif ブートストラップ変数をオーバーライドします。外部 CDN からのフォントの読み込みは避けてください。DNS ルックアップが追加され、制限されたネットワークでは失敗する可能性があります。必要な文字範囲のみを含むフォントのサブセット。
Odoo テーマでダーク モードをサポートするにはどうすればよいですか?
Odoo 18 では、CSS カスタム プロパティを通じてネイティブ ダーク モード サポートが導入されました。 [data-bs-theme="dark"] セレクターを使用して、テーマのダーク モード変数をオーバーライドします。カスタムの色、背景、影の明るいバージョンと暗いバージョンの両方を定義します。ウェブサイト ビルダーのトグルが切り替えを処理します。テーマで必要なのは、両方のモードに適切な CSS 変数値を提供することだけです。
次のステップ
テーマ開発では、デザインとエンジニアリングが融合します。適切に構築された Odoo テーマは、何百もの顧客にサービスを提供し、Odoo App Store で多額の経常収益を生み出すことができます。
関連ガイドについては:
- Odoo Python 開発ガイド — バックエンド開発の基礎
- Odoo カスタム モジュール開発 — モジュール アーキテクチャの概要
- Odoo REST API チュートリアル — API 統合パターン
カスタムの Odoo テーマや Web サイトの再設計が必要ですか? ECOSIRE の Odoo カスタマイズ チーム は、Web サイト ビルダーの完全な統合、すべてのブレークポイントにわたる応答性の高いデザイン、および国際展開のための RTL サポートを備えたエンタープライズ グレードのテーマを設計および構築します。当社は 40 か国以上のクライアントにカスタム テーマを提供してきました。 無料のデザイン相談についてはお問い合わせください。
執筆者
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.
関連記事
AI を活用した顧客セグメンテーション: RFM から予測クラスタリングまで
AI が顧客セグメンテーションを静的な RFM 分析から動的な予測クラスタリングにどのように変換するかを学びます。 Python、Odoo、および実際の ROI データを使用した実装ガイド。
サプライチェーン最適化のための AI: 可視性、予測、自動化
AI を使用してサプライ チェーンの運用を変革します。需要の検知、サプライヤーのリスク スコアリング、ルートの最適化、倉庫の自動化、混乱の予測などです。 2026年のガイド。
B2B E コマース戦略: 2026 年に卸売オンライン ビジネスを構築する
卸売価格設定、アカウント管理、クレジット条件、パンチアウト カタログ、Odoo B2B ポータル構成の戦略を使用して B2B e コマースをマスターします。