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
Requires
- php: >=7.3
- ext-pcre: *
README
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-pcrePHP 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
-
Every statement is a
CssableEvery statement class implements the
Cssableinterface, meaning it can independently render CSS viatoCss().namespace Alnaggar\PhpCssBuilder\Interfaces; interface Cssable { public function toCss(): string; }
-
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
-
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; } }
-
-
CssBlockAtRuleRepresents 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); } } }
-
-
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
- Palestine banner and badge by Safouene1.
License
PhpCssBuilder is open-sourced software licensed under the MIT license.