DEV Community

Cover image for Stop Putting Everything in functions.php: The WordPress File Separation Guide
Julian Neagu
Julian Neagu

Posted on Originally published at visionvix.com

Stop Putting Everything in functions.php: The WordPress File Separation Guide

TL;DR: WordPress gives you three files with distinct jobs: functions.php for logic, style.css for visual rules, and theme.json for design tokens. Mix them up, and your theme becomes unmaintainable. Keep them separate, and you get a scalable design system that works across every block, page, and brand update.

Most developers dump everything into functions.php because it's the first file they learned to edit. Custom fonts go there. Visual styling gets scattered across PHP templates. Color palettes live in hardcoded inline styles. Then someone asks you to change the primary brand color, and you spend two hours hunting through fifteen files trying to find every instance.

This isn't just messy. It breaks the separation of concerns that makes modern WordPress themes maintainable. WordPress gives you three files that each do one job extremely well. When you understand what belongs in each file and why, building themes becomes faster, handoffs become cleaner, and design iterations don't break functionality.

Let's walk through exactly what each file does, what belongs where, and how they work together to create a cohesive design system.

Code editor showing WordPress theme folder with functions.php, style.css, and theme.json files highlighted in sidebar

functions.php Is Your Loader, Not Your Styler

This is the most misunderstood file in WordPress theme development. Developers treat it like a dumping ground for everything, but it has one clear purpose: load resources and enable features. It's a loader, not a styler.

functions.php tells WordPress what exists. It registers capabilities, enqueues assets, and adds hooks. But it should never apply visual presentation directly. No inline styles. No hardcoded CSS rules buried inside PHP functions.

What Actually Belongs in functions.php

Here's the complete list of what this file should handle:

  • Enqueue custom fonts from Google Fonts or local files
  • Enqueue custom CSS files (blog.css, blocks.css, components.css)
  • Enqueue JavaScript files for interactive features
  • Register theme supports (custom logo, post thumbnails, title tag)
  • Register custom image sizes for responsive layouts
  • Register custom Gutenberg block styles and variations
  • Register navigation menus and widget areas
  • Add filters for custom content (reading time, table of contents injection)
  • Add shortcodes for reusable content blocks
  • Register custom blocks with block.json manifests

Let's say you want to load the Inter typeface from Google Fonts. This is exactly what functions.php should do:

php
function visionvix_enqueue_fonts() {
wp_enqueue_style(
'visionvix-fonts',
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap',
false
);
}
add_action('wp_enqueue_scripts', 'visionvix_enqueue_fonts');

This code loads the font into the page's <head> section. That's it. It doesn't apply the font to any element. It doesn't set font sizes. It doesn't touch typography rules. It just makes the resource available.

The same pattern applies when you want to load custom CSS conditionally. Maybe you have specific styling for blog articles that shouldn't load on product pages:

php
function visionvix_enqueue_blog_styles() {
if (is_singular('post')) {
wp_enqueue_style(
'visionvix-blog-styles',
get_template_directory_uri() . '/assets/css/blog.css',
array(),
'1.0.0'
);
}
}
add_action('wp_enqueue_scripts', 'visionvix_enqueue_blog_styles');

This checks if you're viewing a single post, then loads blog.css only on that post type. The visual rules live in the CSS file, not in this PHP function.

If you're working with AI agents to generate or audit content, making your site structure clear and predictable helps those systems understand your intent. Following established patterns like proper JSON formatting best practices and ensuring your markup is AI agent-friendly makes your site more maintainable by both humans and automated tools.

functions.php enables features and registers resources. It never applies visual styling.

This separation is the foundation of maintainability. When you keep functions.php focused on logic and registration, you can swap out entire styling layers without touching functionality. When you mix them, every design change risks breaking a feature. You can't hand off styling to a designer if it's tangled with PHP logic. You can't test visual changes in isolation if they're scattered across procedural code.

Side-by-side comparison showing scattered CSS files (wrong approach) versus organized centralized CSS structure (correct approach)

What Doesn't Belong Here

Never put these in functions.php:

  • Inline CSS rules written inside PHP strings
  • Direct font-family or color declarations
  • Hardcoded spacing values (margin, padding)
  • Layout rules (flexbox, grid, positioning)
  • Typography scales or responsive breakpoints

If it changes how something looks on the page, it doesn't belong in functions.php. Period.

style.css Is Your Paintbrush

This is where every visual rule lives. If it affects how something appears in the browser, it goes in style.css or a CSS file you enqueue from functions.php.

Think of style.css as the styling layer. It applies visual presentation to everything that functions.php loads. The font you enqueued gets applied here. The colors you want to use get declared here. The spacing between headings gets defined here.

What Belongs in style.css

Here's what you should put in your main stylesheet:

  • Global font-family declarations for body, headings, and UI elements
  • Font sizes and weights for your complete typography scale
  • Color variables or direct color values for text, backgrounds, and accents
  • Spacing rules (margin, padding) for consistent vertical rhythm
  • Layout rules (flexbox, grid, positioning) for page structure
  • Blog article typography with reading-optimized line height and spacing
  • Code block styling with syntax highlighting and background colors
  • Highlight boxes, callouts, and blockquote styles
  • Table of contents styling with indentation and hover states
  • Reading time indicator positioning and color
  • Custom dividers and signature footer styling

Let's continue the Inter font example. In functions.php, you loaded the font. Now in style.css, you apply it to actual elements:

``css
body {
font-family: 'Inter', sans-serif;
color: #111;
line-height: 1.7;
font-size: 16px;
}

h1, h2, h3 {
font-weight: 600;
letter-spacing: -0.02em;
margin-top: 2rem;
margin-bottom: 1rem;
}

h1 {
font-size: 2.5rem;
}

h2 {
font-size: 2rem;
}

h3 {
font-size: 1.5rem;
}
``

This is where the visual transformation happens. Every pixel of spacing, every color choice, every typographic detail gets defined here. The user never sees your functions.php code. They see the output of your CSS.

Building a Premium Blog Aesthetic

If you're building a content-focused site, style.css is where you define the reading experience. Code blocks get their dark background and syntax highlighting here. Pull quotes get their border and italic treatment here. The table of contents gets its indentation, numbering, and hover states here.

Here's a concrete example for code blocks:

``css
pre {
background: #1e1e1e;
border-radius: 8px;
padding: 1.5rem;
overflow-x: auto;
margin: 2rem 0;
}

code {
font-family: 'Fira Code', monospace;
font-size: 14px;
line-height: 1.6;
color: #d4d4d4;
}

.hljs-keyword {
color: #569cd6;
}

.hljs-string {
color: #ce9178;
}

.hljs-function {
color: #dcdcaa;
}
``

Every visual detail lives in CSS. No scattered inline styles. No mystery styles buried in block patterns. When you need to change the brand color, you change it in one place. When you need to adjust spacing, you update the CSS variable and it propagates everywhere.

Centralizing your visual rules in style.css means every design change happens in one predictable location.

One pattern I see constantly: developers write inline styles in PHP templates or scatter CSS across multiple files with no naming convention. That's a maintainability nightmare. When you need to debug why a button looks wrong, you shouldn't have to grep through twenty files to find the rule. Centralize your visual rules in style.css or a small set of well-named files like blog.css, blocks.css, or components.css.

theme.json Is Your Design System

This is the modern WordPress approach to design tokens. If you're working with the block editor or building a block theme, theme.json is essential. It's not optional anymore.

Think of theme.json as your design system configuration file. It defines reusable tokens - colors, font sizes, spacing values - that WordPress uses everywhere. The editor uses these tokens. The frontend uses these tokens. Custom blocks inherit these tokens. Core blocks respect these tokens.

What Belongs in theme.json

Here's what this file controls:

  • Global typography scale with named sizes (small, base, large, extra-large)
  • Global color palette with semantic names (primary, secondary, accent, background, text)
  • Global spacing scale with consistent values (tight, base, loose, extra-loose)
  • Global layout rules (content width, wide width for constrained layouts)
  • Block editor defaults (which blocks are enabled, which settings appear in the sidebar)
  • Block editor restrictions (disable custom colors, lock down font sizes)
  • Custom block styles (alternative visual treatments for core blocks)
  • Link color rules (default link color, hover state)
  • Button appearance defaults (padding, border radius, background color)

Here's a simplified example showing the structure:

json
{
"version": 2,
"settings": {
"color": {
"palette": [
{
"slug": "primary",
"color": "#0066cc",
"name": "Primary"
},
{
"slug": "accent",
"color": "#ff6b35",
"name": "Accent"
},
{
"slug": "background",
"color": "#ffffff",
"name": "Background"
},
{
"slug": "text",
"color": "#111111",
"name": "Text"
}
]
},
"typography": {
"fontSizes": [
{
"slug": "small",
"size": "14px",
"name": "Small"
},
{
"slug": "base",
"size": "16px",
"name": "Base"
},
{
"slug": "large",
"size": "24px",
"name": "Large"
},
{
"slug": "extra-large",
"size": "32px",
"name": "Extra Large"
}
],
"fontFamilies": [
{
"slug": "body",
"fontFamily": "'Inter', sans-serif",
"name": "Inter"
}
]
},
"spacing": {
"units": ["px", "rem", "em"],
"spacingScale": {
"steps": 4,
"increment": 0.5,
"unit": "rem"
}
},
"layout": {
"contentSize": "720px",
"wideSize": "1200px"
}
}
}

theme.json file open in code editor displaying typography settings and color palette design tokens in proper JSON format

When you define colors in theme.json, WordPress automatically generates CSS custom properties for you. That primary color becomes --wp--preset--color--primary. You can use it anywhere:

css
.custom-button {
background: var(--wp--preset--color--primary);
color: var(--wp--preset--color--background);
}

The same applies to font sizes and spacing. Define them once in theme.json, use them everywhere through generated CSS variables. No manual coordination needed. The editor sidebar shows these options automatically. Users can pick from your design system without breaking the visual consistency.

theme.json turns your design decisions into reusable tokens that propagate automatically across WordPress.

This is how you scale a design system. When you need to change your primary color, you change one value in theme.json. Every block, every component, every template that uses --wp--preset--color--primary updates instantly. No grep, no find-and-replace, no missed instances.

Why This Matters for Consistency

The block editor gives users a lot of power. They can change colors, adjust font sizes, and add custom spacing. Without theme.json, they get access to every color in the browser color picker. With theme.json, they get a curated palette that matches your brand. They can still customize, but within guardrails.

This is especially important if you're handing off the site to a client. You don't want them choosing random shades of blue that clash with the brand. You want them picking from a defined palette. theme.json enforces that by limiting their options to the colors, font sizes, and spacing values you defined.

How They Work Together

Here's the mental model: functions.php loads the resources, theme.json defines the design system, and style.css applies the visual rules.

When WordPress renders a page, it follows this sequence:

  1. functions.php runs first. It enqueues fonts, CSS files, JavaScript files, and registers theme supports. This sets up the environment.

  2. theme.json generates CSS custom properties. WordPress reads your color palette, font sizes, and spacing scale, then outputs those as CSS variables in the page <head>.

  3. style.css applies visual rules. Your stylesheet uses the fonts that functions.php loaded and the CSS variables that theme.json generated to style every element on the page.

This separation keeps each layer focused on one responsibility. When you need to add a new font, you touch functions.php. When you need to adjust spacing, you touch style.css. When you need to add a new color to the palette, you touch theme.json.

Architecture diagram showing three WordPress theme files: functions.php as loader, style.css as visual applicator, theme.json as design system

A Real-World Example

Let's say you want to add a custom accent color and use it in blog article headings. Here's how you'd implement it across all three files:

Step 1: Define the color in theme.json

json
{
"version": 2,
"settings": {
"color": {
"palette": [
{
"slug": "accent",
"color": "#ff6b35",
"name": "Accent"
}
]
}
}
}

WordPress now generates --wp--preset--color--accent automatically.

Step 2: Use the color in style.css

css
h2 {
color: var(--wp--preset--color--accent);
font-weight: 600;
margin-top: 2rem;
}

Your h2 headings now use the accent color defined in theme.json.

Step 3: No changes needed in functions.php

You didn't need to load anything new or register anything additional. The color token already exists in the system.

This is the ideal workflow. Design changes happen in theme.json. Visual application happens in style.css. Functionality stays in functions.php.

What You Should Put Where

Here's a reference checklist you can use when building or refactoring a WordPress theme:

Put in functions.php:

  • wp_enqueue_style() calls for fonts and CSS files
  • wp_enqueue_script() calls for JavaScript files
  • add_theme_support() for features like custom logo, post thumbnails, title tag
  • register_nav_menus() for navigation menu locations
  • register_sidebar() for widget areas
  • add_action() and add_filter() for custom logic
  • register_block_type() for custom blocks
  • Custom shortcode definitions
  • Reading time calculators, table of contents generators, and similar utilities

Put in style.css:

  • Font-family declarations
  • Font sizes, weights, and line heights
  • Color declarations for text, backgrounds, borders, and accents
  • Margin and padding for spacing rhythm
  • Flexbox and grid layout rules
  • Code block background colors and syntax highlighting
  • Pull quote borders and italic styles
  • Table of contents indentation and numbering
  • Hover states for interactive elements
  • Media queries for responsive breakpoints

Put in theme.json:

  • Color palette definitions with semantic slugs
  • Typography scale with named font sizes
  • Spacing scale with consistent rem values
  • Font family declarations for body and headings
  • Layout constraints (content width, wide width)
  • Block editor feature toggles (disable custom colors, lock font sizes)
  • Custom block style variations
  • Default link colors and button styles

When you follow this structure, your theme becomes modular. You can hand off styling to a designer who only needs to touch style.css. You can onboard a developer who only needs to understand functions.php. You can update design tokens in theme.json without touching code.

This is how professional WordPress themes scale. Not by cramming everything into one file, but by respecting the purpose of each file and keeping concerns separated. When you structure your themes this way, maintenance becomes predictable, handoffs become smooth, and design iterations don't break functionality.

WordPress gives you three files for three jobs. Use them correctly, and your design system will scale with every new block, page, and brand update. Mix them up, and you'll spend more time debugging than building.


📦 Publishing Kit — Dev.to

Title Options (5)

Selected: Stop Putting Everything in functions.php: The WordPress File Separation Guide

Alternates:

  1. functions.php vs style.css vs theme.json: What Actually Belongs Where
  2. WordPress Theme Architecture: Separating Logic, Styles, and Design Tokens
  3. The Three-File WordPress Theme System That Prevents Technical Debt
  4. How to Structure WordPress Themes Using functions.php, style.css, and theme.json

Slug

stop-putting-everything-in-functions-php-wordpress-file-separation-guide

Tags

webdev, tutorial, beginners, wordpress

Top comments (0)