alnaggar/php-css-builder

A simple, fluent, structured CSS builder for PHP.

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/alnaggar/php-css-builder

1.0 2025-12-22 17:56 UTC

This package is auto-updated.

Last update: 2026-01-03 19:29:01 UTC


README

I Stand With Palestine Badge

I Stand With Palestine Banner

Latest Stable Version Total Downloads License

PHP CSS Builder allows you to programmatically generate CSS using a fluent, chainable API.

It supports:

  • Standard CSS rulesets (selectors + declarations)
  • Statement at-rules (e.g. @import, @charset)
  • Block at-rules (e.g. @media, @supports, @layer)
  • Nested CSS statements
  • CSS comments
  • Embedded CSS (<style> blocks)

Warning

This package does not perform any form of sanitization, validation, escaping, or filtering of input values.

It is designed purely as a CSS construction and formatting tool and will output exactly what the developer provides.

All responsibility for security, correctness, and safety of the generated output lies with the developer using this package, especially when handling user-provided or untrusted data.

Table of Contents

Requirements

  • PHP 7.3+
  • ext-pcre PHP extension

Installation

Install the package using Composer:

composer require alnaggar/php-css-builder

Usage & Architecture

This package is a programmatic CSS builder, not a parser, validator, or sanitizer. It allows you to compose CSS using PHP objects, mirroring the actual CSS grammar.

The design is intentionally split into small, focused classes, each representing a real CSS concept.

Core Concepts

  1. Every statement is a Cssable

    Every statement class implements the Cssable interface, meaning it can independently render CSS via toCss().

    namespace Alnaggar\PhpCssBuilder\Interfaces;
    
    interface Cssable
    {
        public function toCss(): string;
    }
  2. CSS Is Built From Statements

    The package models CSS as a tree of statements.

    All CSS statements implement:

    namespace Alnaggar\PhpCssBuilder\Interfaces;
    
    interface CssStatement extends Cssable
    {
        
    }

Statement Types

  1. CssRuleset (Selector Blocks)

    Represents a standard CSS selector block (e.g. :root, *::before, h1).

    Example:

    use Alnaggar\PhpCssBuilder\Statements\CssRuleset;
    
    CssRuleset::make('.card')
        ->comment('Card base styles')
        ->declaration('padding', '1rem')
        ->declaration('border-radius', '6px')
        ->toCss();

    Outputs:

    /* Card base styles */
    .card {
        padding: 1rem;
        border-radius: 6px;
    }

    Nesting support:

    A ruleset can nest:

    • Other rulesets

    • Block at-rules

    This allows output suitable for CSS preprocessors:

    use Alnaggar\PhpCssBuilder\Statements\CssRuleset;
    
    CssRuleset::make('.card')
        ->declaration('padding', '1rem')
        ->nestedStatement(
            CssRuleset::make('&:hover')
                ->declaration('background', '#eee')
        )
        ->toCss();

    Outputs:

    .card {
        padding: 1rem;
        &:hover {
            background: #eee;
        }
    }
  2. CssBlockAtRule

    Represents at-rules that contain blocks (e.g. @media, @supports, @layer, @page, @top-right).

    Example:

    use Alnaggar\PhpCssBuilder\Statements\CssBlockAtRule;
    use Alnaggar\PhpCssBuilder\Statements\CssRuleset;
    
    CssBlockAtRule::make('media', '(max-width: 768px)')
        ->nestedStatement(
            CssRuleset::make('.card')
                ->declaration('font-size', '14px')
        )
        ->toCss();

    Outputs:

    @media (max-width: 768px) {
        .card {
            font-size: 14px;
        }
    }

    Nesting support:

    A block at-rule can nest:

    • Rulesets

    • Other block at-rules

    Example:

    use Alnaggar\PhpCssBuilder\Statements\CssBlockAtRule;
    use Alnaggar\PhpCssBuilder\Statements\CssRuleset;
    
    CssBlockAtRule::make('layout', 'components')
    ->nestedStatement(
        CssBlockAtRule::make('supports', '(backdrop-filter: blur(10px))')
            ->nestedStatement(
                CssRuleset::make('.card-glass')
                    ->declaration('backdrop-filter', 'blur(10px)')
                    ->declaration('background', 'rgba(2, 6, 23, 0.7)')
            )
    )->toCss()

    Outputs:

    @layer components {
        @supports (backdrop-filter: blur(10px)) {
            .card-glass {
                backdrop-filter: blur(10px);
                background: rgba(2, 6, 23, 0.7);
            }
        }
    }
  3. CssStatementAtRule (Single-Line At-Rules)

    Represents non-block at-rules (e.g. @import @charset @namespace).

    Example:

    use Alnaggar\PhpCssBuilder\Statements\CssStatementAtRule;
    
    CssStatementAtRule::make('import', 'url("theme.css")')->toCss();

    Outputs:

    @import url("theme.css");

Nestable Statements

Only statements that implement NestableCssStatement can be nested.

namespace Alnaggar\PhpCssBuilder\Interfaces;

interface NestableCssStatement extends CssStatement
{
        
}

These are:

CssRuleset

CssBlockAtRule

This ensures:

  • Invalid CSS structures are not accidentally created
  • Nesting mirrors real CSS grammar
  • Output can safely target preprocessors

EmbeddedCss (HTML <style> Container)

EmbeddedCss is the final output container.

It:

  • Collects multiple CSS statements
  • Outputs a valid <style> HTML block
  • Supports optional <style> tag attributes

Complete Example:

use Alnaggar\PhpCssBuilder\Html\EmbeddedCss;

/*
|--------------------------------------------------------------------------
| Create embedded stylesheet
|--------------------------------------------------------------------------
*/

$embeddedCss = EmbeddedCss::make();

/*
|--------------------------------------------------------------------------
| Style tag attributes
|--------------------------------------------------------------------------
*/

$embeddedCss->attribute('nonce', 'nonce-123')
    ->attribute('id', 'embedded-css');

/*
|--------------------------------------------------------------------------
| Comment
|--------------------------------------------------------------------------
*/

$embeddedCss->comment(
    'Base document styles'
);

/*
|--------------------------------------------------------------------------
| Statement at-rules
|--------------------------------------------------------------------------
*/

$embeddedCss->statements([
    CssStatementAtRule::make('charset', '"UTF-8"'),
    CssStatementAtRule::make('import', 'url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap")')
]);

/*
|--------------------------------------------------------------------------
| Font face
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statement(
    CssBlockAtRule::make('font-face', null)
        ->declaration('font-family', '"Panel Font"')
        ->declaration('src', 'url(\'assets/fonts/panel.woff2\') format(\'woff2\')')
        ->declaration('font-weight', 'normal')
        ->declaration('font-style', 'normal')
);

/*
|--------------------------------------------------------------------------
| Base styles
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statements([
    CssRuleset::make(':root')
        ->declaration('--text-primary-color', '#DBEAFE')
        ->declaration('--bg-primary-color', '#2563EB')
        ->declaration('--text-secondary-color', '#111827')
        ->declaration('--bg-secondary-color', '#F3F4F6')
        ->declaration('--radius', '0.5rem'),

    CssRuleset::make('*, *::before, *::after')
        ->declaration('box-sizing', 'border-box'),

    CssRuleset::make('body')
        ->comment('Reset & typography')
        ->declaration('margin', '0')
        ->declaration('font-family', '"Panel Font", "Inter", system-ui, sans-serif')
        ->declaration('background', '#0f172a')
        ->declaration('color', '#e5e7eb')
        ->declaration('line-height', '1.6')
]);

/*
|--------------------------------------------------------------------------
| Utilities
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statements([
    CssRuleset::make('.text-primary')
        ->declaration('color', 'var(--text-primary-color)'),
    CssRuleset::make('.bg-primary')
        ->declaration('background', 'var(--bg-primary-color)'),
    CssRuleset::make('.text-secondary')
        ->declaration('color', 'var(--text-secondary-color)'),
    CssRuleset::make('.bg-secondary')
        ->declaration('background', 'var(--bg-secondary-color)'),
]);

/*
|--------------------------------------------------------------------------
| Components
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statements([
    CssRuleset::make('.panel')
        ->declaration('padding', '1.5rem')
        ->declaration('border-radius', 'var(--radius)')
        ->declaration('background', '#020617'),

    CssRuleset::make('.panel-title')
        ->declaration('font-size', '1.25rem')
        ->declaration('font-weight', '600')
        ->declaration('margin-bottom', '0.75rem'),

    CssRuleset::make('.btn')
        ->declaration('display', 'inline-flex')
        ->declaration('align-items', 'center')
        ->declaration('gap', '0.5rem')
        ->declaration('padding', '0.5rem 0.75rem')
        ->declaration('border-radius', 'var(--radius)')
        ->declaration('border', 'none')
        ->declaration('cursor', 'pointer')
]);

/*
|--------------------------------------------------------------------------
| Animations
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statement(
    CssBlockAtRule::make('keyframes', 'fade-in')
        ->nestedStatement(
            CssRuleset::make('from')
                ->declaration('opacity', '0')
        )
        ->newline()
        ->nestedStatement(
            CssRuleset::make('to')
                ->declaration('opacity', '1')
        )
);

$embeddedCss->newline();

$embeddedCss->statement(
    CssRuleset::make('.fade-in')
        ->declaration('animation', 'fade-in 200ms ease-in-out')
);

/*
|--------------------------------------------------------------------------
| Media queries
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statement(
    CssBlockAtRule::make('media', '(max-width: 768px)')
        ->nestedStatements([
            CssRuleset::make('.panel')
                ->declaration('padding', '1rem'),

            CssRuleset::make('.panel-title')
                ->declaration('font-size', '1.1rem')
        ])
);

/*
|--------------------------------------------------------------------------
| Feature queries
|--------------------------------------------------------------------------
*/

$embeddedCss->newline();

$embeddedCss->statement(
    CssBlockAtRule::make('supports', '(backdrop-filter: blur(10px))')
        ->nestedStatement(
            CssRuleset::make('.panel-glass')
                ->declaration('backdrop-filter', 'blur(10px)')
                ->declaration('background', 'rgba(2, 6, 23, 0.7)')
        )
);

return $embeddedCss->toHtml();

Outputs:

<style nonce="nonce-123" id="embedded-css">
    /* Base document styles */
    @charset "UTF-8";
    @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap");

    @font-face {
        font-family: "Panel Font";
        src: url('assets/fonts/panel.woff2') format('woff2');
        font-weight: normal;
        font-style: normal;
    }

    :root {
        --text-primary-color: #DBEAFE;
        --bg-primary-color: #2563EB;
        --text-secondary-color: #111827;
        --bg-secondary-color: #F3F4F6;
        --radius: 0.5rem;
    }
    *, *::before, *::after {
        box-sizing: border-box;
    }
    body {
        /* Reset & typography */
        margin: 0;
        font-family: "Panel Font", "Inter", system-ui, sans-serif;
        background: #0f172a;
        color: #e5e7eb;
        line-height: 1.6;
    }

    .text-primary {
        color: var(--text-primary-color);
    }
    .bg-primary {
        background: var(--bg-primary-color);
    }
    .text-secondary {
        color: var(--text-secondary-color);
    }
    .bg-secondary {
        background: var(--bg-secondary-color);
    }

    .panel {
        padding: 1.5rem;
        border-radius: var(--radius);
        background: #020617;
    }
    .panel-title {
        font-size: 1.25rem;
        font-weight: 600;
        margin-bottom: 0.75rem;
    }
    .btn {
        display: inline-flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0.5rem 0.75rem;
        border-radius: var(--radius);
        border: none;
        cursor: pointer;
    }

    @keyframes fade-in {
        from {
            opacity: 0;
        }

        to {
            opacity: 1;
        }
    }

    .fade-in {
        animation: fade-in 200ms ease-in-out;
    }

    @media (max-width: 768px) {
        .panel {
            padding: 1rem;
        }
        .panel-title {
            font-size: 1.1rem;
        }
    }

    @supports (backdrop-filter: blur(10px)) {
        .panel-glass {
            backdrop-filter: blur(10px);
            background: rgba(2, 6, 23, 0.7);
        }
    }
</style>

Contributing

If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request on the GitHub repository.

Credits

License

PhpCssBuilder is open-sourced software licensed under the MIT license.