Changelog
phil/ux-kitAll notable changes to phil/ux-kit will be documented in this file.
Updates should follow the Keep a CHANGELOG principles.
0.5.0-alpha - 2026-11-12
Added
- On
ImageandPicture:captionPosition,belowby default, oroverlay, which lays the caption, its credit, its license and its creation over the foot of the image on a veil dark enough for light text whatever the picture, the figure of classhasOverlayCaption, as wide as its image, a description staying under both;Image:Figuretakes it. - On
ImageandPicture:frame, a frame drawn from the tokens of phil-ux, words separated by spaces,rounded(hasRounded),framed(hasFramed) andraised(hasRaised), ortoken(hasToken), the picture covering a square box cropped to a disc inside a ring, for a pawn or an avatar; an unknown word is left out. - On
ImageandPicture:pixelated, pixel art enlarged without blurring its pixels (hasPixelated). - On
ImageandPicture:transition, the name of the image in a view transition, written indata-transitionwith the classhasTransition, which phil-ux reads intoview-transition-namethrough a typedattr(), never astyleattribute; a name that is no CSS identifier, or a keyword ofview-transition-name, is left out. The image README shows its use with Turbo 8 and with cross-document view transitions, and its limit: a name stands once in a page. - On
ImageandPicture:structured, which describes the image as a schema.orgImageObjectthroughimage_json_ld()andphil/structured-data, suggested: its absolute address, made by Symfony'sUrlHelperand read byphil/url, its width and height, caption, credit ascreditText, license address and creation asdigitalSourceType; added to the graph of the page when aNodeCollectorgathers one, written otherwise in a JSON-LD script of its own before the image, without a nonce. - On
Gallery:layout="carousel", its figures in one row that scrolls and snaps without a script, the list of classhasLayoutCarouselreached by the keyboard throughtabindex="0"; where the browser draws them, a marker per figure by::scroll-marker, named by thedata-markerof itsli, the text alternative of its image or its rank, and a button on each side by::scroll-button(), named bypreviousLabelandnextLabelthrough thedata-previousanddata-nextof the list;captionPosition, carried to eachPicture. Gallerycarriesframeandpixelatedto every image, and each image its owntransition;Lightboxcarriesframeandtransitionto its thumbnail andpixelatedto both its images;Hotspotscarriesframe, never a token, andpixelated.- The recipe
Hotspots: an image of theimagerecipe with points of interest, a list of links named bylabelover it, each point placed by the classeshasHotspotX0tohasHotspotX100andhasHotspotY0tohasHotspotY100, to the nearest five per cent, and by itsdata-xanddata-y, read exactly by a typedattr()where the browser does, never by an inline style; a point without words, without an address, with a refused address or without a place left out; the caption, credit and license in a figure. - The recipe
Hero: an image across the whole box that stays a real image, aPicturelaid outfull-widthand covering,priorityby default, so loaded at once, fetched first and preloaded through WebLink;size(screen,large,auto) andalign(start,center,end); its content over the image on a veil. - The recipe
Video: avideowith the controls of the browser,preload="none"by default ormetadata, never an autoplay, its poster checked as an image, its sources and WebVTT tracks (captionsby default,subtitles,descriptions,chapters) checked byimage_origin(), adata:source or track refused, a link to the first source for a browser that plays no video;transcriptandtranscriptLabel, a disclosure the video is described by; the caption, credit, license and creation in a figure. - The recipe
Figure: several images in a grid of classisFigureGroup, as many columns as the container allows orcolumns(2,3,4), under one caption, credit, license and creation. image_filters(), when LiipImagine is installed: thesrc,srcset,sources,widthandheightof aPicturefrom the filter sets applied to one image, each set a candidate of the width its last resizing filter gives, or of the one the template gives; the sets of AVIF and WebP make the sources, the others the fallback, whose widest set givessrc, and the size of the image when it crops exactly.liip/imagine-bundleis suggested;ImageFiltersPassleaves the bridge out when the bundle is not registered.picture_props()and the interfacePhil\UxKit\Application\Contract\PicturePropsProvider, which autoconfiguration tagsphil_ux_kit.picture_props: the props of aPicturefor an object of the application, from the first provider that supports it, kept to those that describe the image, a map of options handed to it.- The recipe
Celebration: a message said in a polite live region, and confetti or fireworks (effect) drawn over the page on a canvas by its new Stimulus controllercelebration, without a library, in the colours of the tokens of phil-ux resolved through the CSSOM, so those of the theme; it plays as soon as the page shows it withautoplay, on the DOM eventcelebratedispatched anywhere on the page, itsdetailgiving themessageand theeffectof this time, and by the actioncelebration#play, whose params do the same; a blockcontentholds what plays it. Where motion is reduced, by the system or bydata-motion="reduce", only the message shows. No sound; the canvas, cloned from atemplate, hidden from screen readers and set over the page through the CSSOM without taking a click, safe under a strict Content Security Policy;durationkept between 600 and 4000 milliseconds, held while the tab is hidden; one animation at a time; fireworks burst twice a second at most; the canvas and its bitmap removed at the end or when the element leaves the page; the eventscelebration:startandcelebration:end. phil:ux-kit:migrate-images, with the Console component: theImageandPicturerecipes proposed in place of theimgandpictureelements of the Twig templates of a project, or what stands in the way of each;--writerewrites the proposals only.Phil\UxKit\Application\ImageMigrationandImageMigrationFinding.doc/images.md: choosinglayoutandsizes, case by case, and the checklist of the largest contentful paint; in French indoc/fr/images.md. The image README says how the image behaves under a strict policy and on paper; the celebration README, under a strict policy.RecipesPagecopies the files of themediadirectory of the resources undermedia/of the web root: the browser suite plays a two-second WebM clip with its WebVTT captions.RecipesPagewrites each mode under a strict Content Security Policy too,strict.html,strict-dark.htmlandstrict-rtl.html:default-src 'self', no'unsafe-inline'for scripts or styles, the inline scripts the page runs allowed by a nonce of its own,img-src 'self' data:, in ametaelement first in the head, with a watcher,strict.js, that lists inwindow.violationswhat the browser refuses; the template readsstrict; thepage.cssof the resources, when there is one, is copied beside the page.RecipesPage::POLICYis the policy.BrowserTestCase::strictModes(), the modes under the strict policy, on whichopen()and the end of a test fail on anything the browser refused;isStrict();isRightToLeft()knowsstrict-rtl.html.- The browser suite audits with axe-core, in clear, in dark and right to left, the caption over its image, the gallery
laid out as a carousel, the image with its points, the hero, the video, the figure of several images, the frames and
the celebration; it checks the caption on the foot of its image, the row of the carousel scrolled at the arrows, each
point at its place and under the image on a phone, the hero covered by its image, the video fetching nothing before it
is played and playing its file, the figure in its columns, the frames, the token, the pixel art and a view transition
moving an image under its name, the images on paper, the medium emulated, and the images right to left: a caption, a
gallery and a carousel starting on the right, the button at the start of a carousel pointing to the right, the close
button of a lightbox in the top left corner; and the celebration: the message said, the canvas shown then removed,
nothing drawn under reduced motion, the event
celebrateand the button of the demonstration. - The browser suite runs every test of the images and of the celebration under the strict policy too, finds nothing
refused on any strict page and sees a
styleattribute written afterwards refused. - Visual tests: each layout of the images, an image
fixed,constrained,full-widthand cropped to a ratio, a gallery in a grid and a figure of three columns, compared in the browser suite with a reference screenshot kept intests/Browser/screenshots/, a few hundred bytes each.Phil\UxKit\Test\Screenshotscompares, on GD, a screenshot shrunk to a scale of its CSS width with its reference, pixel by pixel within a threshold, the share of changed pixels within a tolerance;BrowserTestCase::assertLooksLike()andscreenshots(), the directory of a suite's references, a missing one written and the test marked incomplete, all of them written again withPHIL_UX_UPDATE_SCREENSHOTS=1. tests/Benchmark/GalleryBench.php: the rendering of a gallery of 100 images with PHPBench, in a grid, with lightboxes, deferred and with the sources ofPicture.ext-gd, for the screenshots, andsymfony/console, for the command, as development requirements, and suggested;symfony/yamlas a development requirement, for the routes the functional tests of LiipImagine load when the package is installed on its own.
Changed
AspectRatiowrites nostyleattribute: a common ratio is a class,hasRatio16x9and the others, any other its width and height indata-ratio-widthanddata-ratio-height, which phil-ux reads through a typedattr().Badgewrites itscolorindata-color, never in astyleattribute.- Both hold under a strict Content Security Policy, and stand on the strict pages of the browser suite, which measures the ratio and reads the colour.
- The page of the browser suite has no
styleattribute left, its own rules intests/Browser/resources/page.css. - The image recipe requires
phil/ux-kit:^0.5@dev;manifest.jsonrequiresphil/ux-css:^0.5@dev; the branch alias ofdev-mainis0.5.x-dev.
Fixed
- The dates of 0.3.0-alpha and 0.4.0-alpha, both released on 2026-11-11.
0.4.0-alpha - 2026-11-11
Added
- On
ImageandPicture:deferred,fetchpriority="low"for an image far below the fold,prioritywinning over it;placeholder, the colour of the box while the image loads, a quiet tone of the tokens (neutral,brand,red,green,amber,blue,violet) orcustom, written as the classeshasPlaceholderBlueand the others, never as a style;preview, a tiny raster image inline in base 64, handed with the classhasPreviewto the new Stimulus controllerimage-previewof the recipe, which paints it blurred, in an inline SVG at the ratio of the image, behind the image through the CSSOM until it has loaded or failed, safe under a strict Content Security Policy;descriptionanddescriptionLabel, a long description in adetailsof classisDescriptionafter the image and before its caption, the image described by its text througharia-describedby, beside a description of the page, under the identifier of the image followed by-descriptionor one of its own. - On
PictureandLogo:forced, the variant drawn for forced colours, offered first whereforced-colorsis active, as a URL, a map of densities or sources of their own types onPicture; never preloaded. - On
Gallery:deferred, its figures off screen skipped withcontent-visibility: autothrough the classisDeferred, eachlicarrying thedata-widthanddata-heightof its crop or of its image, and its images fetched last;placeholderfor every image;preview,descriptionandidper image;descriptionLabel. - On
Lightbox:deferred,placeholderandpreviewfor the thumbnail;descriptionanddescriptionLabel, the description after the thumbnail, outside its button, which it describes. - On
Logo:priority,layout,aspect,fitandposition, carried to itsPicture. OnComingSoon:logoWidth,logoHeight,logoPriority,logoLayout,logoAspect,logoFitandlogoPosition, carried to itsLogo, andillustrationLoading, carried to itsIllustration, so that the page can preload its logo and load its illustration at once; by default, the page is unchanged. image_check(), called byImage: whilephil_ux_kit.image.checkis on, by default with the debug of the kernel,ImageTextCheckfinds a text alternative that is the name of the file (read byphil/url,@2x,@480wand the digest of AssetMapper set aside), a word that only says it is an image or a text that starts by saying so, in English and in French, and a missing width or height, and each is logged as a warning on thephil_ux_kitchannel; the page is unchanged.- The browser suite audits with axe-core the picture with its variant for forced colours, its placeholder and its description, the image with its preview and the deferred gallery with a lightbox open; it checks the ratio of the figures a deferred gallery skips and its focus ring, the preview painted until its image arrives, and the variant shown where colours are forced.
Changed
- The image recipe copies the
image-previewcontroller and requiressymfony/stimulus-bundleandphil/ux-kit:^0.4@dev;Image:Figuremakes a figure for a description alone, and writes itsfigcaptiononly with a caption, a credit, a license or a creation. composer.jsonrequirespsr/log, for the warnings ofimage_check();manifest.jsonrequiresphil/ux-css:^0.4@dev; the branch alias ofdev-mainis0.4.x-dev.
0.3.0-alpha - 2026-11-11
Added
PhilUxKitBundle, the package being a Symfony bundle: the Twig functions the image recipes call, configured underphil_ux_kit.image(hosts,allow_localhost).image_origin()says where the image at an address, or every candidate of asrcset, comes from:localfor a relative path, adata:image/one or the page's own origin,externalfor a host of the list or under one, read byphil/url,refusedfor any other host, credentials, an IP address,localhostunless allowed, or any other scheme.image_sources()gives thesrc,srcset,sources,widthandheightof aPicturefrom the files of one image among the assets of AssetMapper (logo.avif,logo@2x.avif,logo.webp,logo.png…), with the optionsdensities,widths,formatsandfallbacks.- On
ImageandPicture:preload, the main image preloaded throughpreload()of Symfony WebLink with itsimagesrcset,imagesizes,fetchpriority,crossoriginandreferrerpolicy, by default withpriority; aPicturepreloads its first source with itstype, and nothing with a media query.credit,licensewithlicenseHref(rel="license"),creation, a code of the IPTC Digital Source Type vocabulary or an enum whose value is one, andcreationLabel, written under the caption insmallelements of classesisCredit,isLicenseandisCreation, through the newImage:Figurecomponent. - On
PictureandLogo:dark, offered whereprefers-color-schemeis dark, andstill, the still image of an animated one, offered whereprefers-reduced-motionis reduce, each a URL, a map of densities, or sources of their own types onPicture, written before the other sources. - The recipe
Gallery: a list named bylabelin adivof classisGallery, its figures in a grid that follows its container,aspect,fitandsizesfor every image, each opened in aLightboxwithlightbox. - The recipe
Lightbox: a thumbnail in a button that opens a native modaldialogbycommand="show-modal"andcommandfor, closed by Escape, its button andclosedby="any", its Stimulus controllerlightboxstanding in where the browser runs neither; the full image shown whole, the caption under both, the labels as props. - The browser suite audits the images with axe-core 4.14.0, against the rules of WCAG 2.2 A and AA, in clear, in dark and right to left; it opens a lightbox, lays out a gallery and follows the colour scheme and the motion of the system.
Changed
- The image recipe refuses an image of another host unless
phil_ux_kit.image.hostslists it, and fetches one of a listed host withreferrerpolicy="no-referrer"; it requiresphil/ux-kitandsymfony/web-link.prioritynow also preloads the image. manifest.jsonrequiresphil/ux-css:^0.3@dev;composer.jsondeclares the package asymfony-bundle, requiresphil/urland the Symfony components of the bundle, and autoloadsPhil\UxKit\fromsrc/; the branch alias ofdev-mainis0.3.x-dev.
0.2.0-alpha - 2026-11-11
Added
- The recipe
image, under the prop names of Symfony UX Image, without a service, JavaScript or inline style:Image, oneimgof classisImage, andPicture, itssources({type, srcset, sizes, media}, AVIF first) before thatimg;src,alt,widthandheightrequired, an emptyaltmaking the image decorative, itstitleandaria-*attributes then left out;srcsetas HTML writes it or a map of densities, or of widths withsizes;sizesafterauto,while the image loads lazily;loading="lazy"anddecoding="async"by default,prioritygivingloading="eager"andfetchpriority="high";caption, afigurewith its escapedfigcaption, shown under a decorative image too; of the other attributes, onlyid,class,title,crossorigin,referrerpolicy,itemprop,draggable,data-*andaria-*, written withhtml_attr()oftwig/html-extra, which the recipe requires withtwig/extra-bundle; ajavascript:,vbscript:ordata:URL other thandata:image/refused, a refusedsrcrendering nothing, a refusedsrcsetor source left out whole. - On
ImageandPicture:layout,fixed,constrainedorfull-width, which computessizesfromwidthwhen none is given ((min-width: <width>px) <width>px, 100vwconstrained,100vwfull width; a fixed layout reads its maps as densities) and writeshasLayoutFixedorhasLayoutFullWidth;aspect(16/9,4/3,3/2,1/1,21/9,3/4,2/3,9/16, orcustom, read from--phil-image-ratio),fit(coverby default with a ratio, orcontain) andposition(center,top,bottom,left,right), written as classes of phil-ux, never as an inline style. - Art direction on
Picture: the sources with amediaquery come first, in the order of their first query, each group AVIF, then WebP, then the other types, then the sources without a query; each source takes thewidthandheightof its crop; a query holding<,>,{,}or;leaves its source out, a size that is not a positive integer is left out.
Changed
Logorenders through thePictureof theimagerecipe, on which it now depends: itsimgcarriesisImage, a refused URL is left out as inPicture;webpalso takes a map of densities, and the newavifandsrcsetprops add an AVIF source offered first and the densities of the fallback,{theme}and{locale}replaced in each.manifest.jsonrequiresphil/ux-css:^0.2@dev; the branch alias ofdev-mainis0.2.x-dev.
0.1.1-alpha - 2026-11-11
Published by mistake with the content of 0.1.0-alpha; use 0.2.0-alpha.
0.1.0-alpha - 2026-11-11
Added
manifest.jsonandicon.svg: the kit for the Symfony UX Toolkit, depending onphil/ux-css(Composer) and@phil/ux-css(npm, importmap) and on Symfony UX Twig Component.- The recipe
Logo: an image with an optional WebP source,{theme}and{locale}replaced in its sources, linked when it has anhref;altrequired. - The recipe
AnimatedHeadlineand its Stimulus controlleranimated-headline: the words of a headline revealed through a clip that closes to a caret and opens on the next one; one word revealed once, then still; several played through once back to the first, the run fitted within five seconds bytiming()andBUDGET(WCAG 2.2.2); withloop, cycled, with a pause/play<button>whose labelspauseLabelandplayLabelare props, the heading named by its hidden copy througharia-labelledby; held fully open while hovered, focused, paused or in a hidden document; each move ended ontransitionendor after its duration; still underprefers-reduced-motionand without JavaScript; read whole by screen readers from a visually hidden copy, the animated words hidden andaria-live="off". - The recipe
Illustration: a figure with a WebP source and its fallback, one picture chosen among candidates on the server, at random or by a seed; caption, sizes, loading. - The recipe
AutosaveStatus: the live region and the notice of a form's draft, for the autosave controller ofphil/forms-autosave, its targets and actions; the labels of its buttons as props. - The recipes
ReactionButtonandReactionPicker, forphil/reactions-bundle: a reaction pressed or not, or as plain text, with a touch target of 2.75rem; the other reactions in a disclosure of plain buttons, with the targets of the picker controller of the bundle, the texts as props, and of the attributes of a button onlyname,value,title,data-*andaria-*. - The recipes
Badge,Button,ButtonGroup,Card,Empty,Popover,Tabs,Accordion,Dialog,Sheet,Switch,Tooltip,Pagination,Carousel,Spinner,Skeleton,Alert,Avatar,Input,Label,Select,Separator,Textarea,Checkbox,Breadcrumb,DropdownMenuandTable, under the names, props, blocks and Stimulus controllers of the Shadcn kit of the Toolkit, on semantic elements and the classes of phil-ux; every text a prop or a block.DialogandSheetondialog, HTML first:closedby="any"and the invokerscommandandcommandfor, which the controllerdialogleaves to the browser where it runs them and stands in for where it does not;Popover,SelectandDropdownMenuon the popover API,Tooltipon a manual popover,Accordionondetailsandsummary,Switchoninput role="switch",Carouselon native scroll snapping, the keyboard of WAI-ARIA in each; every part writes its class of phil-ux,data-slotkept beside it. - The recipe
QrCode, around the component ofphil/qr-code-bundle. - The recipes
AlertDialog,AspectRatio,Collapsible,Field,Item,Kbd,NativeSelect,Progress,RadioGroup,ScrollArea,ToggleandToggleGroup, under the names, props and blocks of the Shadcn kit, HTML first:AlertDialogondialogwith the rolealertdialog,closedby="closerequest", the invokers and the controllerdialog, its cancel button focused;Collapsibleondetails;Kbd,NativeSelectandProgresson their native elements;RadioGroup,ToggleandToggleGroupon radio buttons and checkboxes, without a controller;ScrollAreaa region the keyboard reaches when it is named;AspectRatiorefusing any ratio but a number or a quotient. - The block
ComingSoon: the logo, an eyebrow and the animated headline in anhgroup, paragraphs and a content block in the left column, asheaderandmainlandmarks or not; an illustration filling the right column with a slanted entrance; stacked on small screens. - The blocks
LoginFormandSignupForm, of plain form elements in a card: the field names of the form login of Symfony Security, the last user name written back, the error a live alert the fields name, the CSRF token, the links to a forgotten password and to a sign-up; an optional name, the email, the new password with its rule and its repetition, the errors of each field tied to it, a password never written back; every word a prop. .gitattributes: the archiveux:installdownloads holds the recipes andsrc/, the package's other files left out.- The recipes
ColorPicker,DatePicker,CopyButton,AnimatedButton,Stepper,ActivityCalendar,Tree,Rating,NumberInput,FileInput,Tag,RelativeTime,ProgressRing,SparklineandBeforeAfter, of this kit alone, with the controllers they need; every part with its class of phil-ux, underprefers-reduced-motion, right to left, at 375 pixels, and at the keyboard of WAI-ARIA or of the native control they stand on; each works, or shows what it can, without a script. - The recipes
ChartandChartJs, of this kit alone.Chartdraws bars or lines in SVG on the server, on a scale of four to six plain steps whatever the values, a missing value breaking the line, the last 366 labels and the first six series kept, each series in a tone of phil-ux, its line from the second on with a dash of its own, and a legend;ChartJsputs the canvas of Symfony UX Chart.js in the same figure. The drawing hidden, both write the table of their values, read by screen readers or shown in a disclosure. Tooltip: the controller writes on the bubble the side it went to,isTop,isBottom,isStartorisEnd, for phil-ux to draw the arrow, and keeps the arrow on the trigger when the bubble is pushed back in the viewport.- The suite
tests/Browser, on the real@hotwired/stimuluspinned in its ownpackage.json. Phil\UxKit\Test\BrowserTestCaseandPhil\UxKit\Test\RecipesPageinsrc/Test/, autoloaded: the headless Chrome and the page of every recipe in clear, in dark and right to left, for the browser suites of the kits built on the same controllers; the build of Stimulus is the suite's own;clickBeside(), a real click on the backdrop beside an element.