Apple’s Safari 27.3 beta adds a batch of CSS features that move logic from JavaScript and preprocessors into plain CSS: custom functions with @function, inline conditionals with if(), and the typed attr() function. With Safari joining in, these are worth learning now. This tutorial explains each one with small, practical examples, shows how to ship them with safe fallbacks, and lists the other CSS additions in the release.
What Safari 27.3 beta adds
From Apple’s Safari 27.3 beta release notes, the CSS highlights include:
- The
@functioncustom functions rule. - The
if()function. - The
attr()function. - Also
random-item(),ident(),inherit(),corner-shape,object-view-box,hyphenate-limit-chars,@supports at-rule(...)conditions,path()andshape()inshape-outside, and a custom<string>value fortext-overflow.
It is a beta, so test in Safari Technology Preview or the beta build, and check current support on MDN before relying on any of these without a fallback.
1. @function: reusable calculations in CSS
Custom properties store values. @function lets you store a calculation with parameters and call it like a built-in function. The name must start with two dashes, and the body returns a value with the result descriptor.
/* A simple function: negate any length */
@function --negate(--value) {
result: calc(-1 * var(--value));
}
.pull-up {
margin-top: --negate(1.5rem); /* -1.5rem */
}
Parameters can be typed and have defaults, and you can declare the return type:
/* Lighten a colour by mixing in white; amount defaults to 20% */
@function --tint(--color <color>, --amount <percentage>: 20%) returns <color> {
result: color-mix(in oklch, var(--color), white var(--amount));
}
/* Fluid type between a min and max size */
@function --fluid(--min <length>, --max <length>) returns <length> {
result: clamp(var(--min), calc(var(--min) + 2vw), var(--max));
}
:root { --brand: #2563eb; }
.button {
background: var(--brand);
border-color: --tint(var(--brand), 40%);
font-size: --fluid(1rem, 1.25rem);
}
.button:hover {
background: --tint(var(--brand));
}
This replaces a common reason to use Sass functions. The difference is that CSS functions are evaluated live in the browser, so they react to custom properties changed by media queries, themes or JavaScript.
Safari’s notes also mention fixes so that a function body inherits custom property values from the calling element, and that typed parameters keep their type, which is the behaviour you would expect from the examples above.
2. if(): inline conditionals
if() picks a value based on a condition, right inside a declaration. Conditions can be style() queries on custom properties, media() queries or supports() checks, followed by an optional else branch:
.alert {
/* Theme via a custom property */
background: if(
style(--variant: danger): #fee2e2;
style(--variant: success): #dcfce7;
else: #f1f5f9
);
/* Layout via a media query, without a separate @media block */
padding: if(media(width >= 700px): 1.5rem 2rem; else: 1rem);
}
<div class="alert" style="--variant: danger">Payment failed.</div>
<div class="alert" style="--variant: success">Saved!</div>
<div class="alert">Heads up: maintenance tonight.</div>
The win is locality. Instead of three modifier classes or a separate media block, the logic for one property sits in one place. Keep conditions simple, because long if() chains get hard to read quickly.
3. attr(): use HTML attributes as typed values
For years, attr() only worked inside content and always returned a string. The modern version can be used in any property and can parse the attribute as a specific type, with a fallback:
<ul class="grid" data-columns="4">...</ul>
<div class="bar" data-progress="72"></div>
<p class="note" data-accent="tomato">Typed attributes!</p>
.grid {
display: grid;
/* Parse as an integer; fall back to 3 columns */
grid-template-columns: repeat(attr(data-columns type(<integer>), 3), 1fr);
gap: 1rem;
}
.bar {
height: 0.5rem;
background: #16a34a;
/* Treat the number as a percentage */
width: attr(data-progress %, 0%);
}
.note {
border-left: 4px solid attr(data-accent type(<color>), currentColor);
padding-left: 0.75rem;
}
This is handy for server-rendered pages and CMS output, where you can set a data attribute in a template without writing inline styles. If the attribute is missing or does not parse as the requested type, the fallback is used.
Shipping these safely
Browsers that don’t understand a value drop that declaration and keep the previous one. So write a plain fallback first, then the modern version:
.grid {
grid-template-columns: repeat(3, 1fr); /* fallback */
grid-template-columns: repeat(attr(data-columns type(<integer>), 3), 1fr); /* modern */
}
.alert {
background: #f1f5f9; /* fallback */
background: if(style(--variant: danger): #fee2e2; else: #f1f5f9); /* modern */
}
For whole blocks, use feature queries. Safari 27.3 beta also adds @supports at-rule(...), which can test for at-rules like @function. Since support for that condition may vary, a plain value check is a safer default:
@supports (width: if(else: 1px)) {
/* if()-based styles here */
}
Always test in at least one browser that lacks the feature to confirm the fallback looks acceptable.
Other additions worth a look
text-overflowwith a custom string, so you can truncate with something other than an ellipsis.corner-shapefor corners beyond simple rounding.object-view-boxto crop or zoom images and videos in CSS.hyphenate-limit-charsto control minimum word length and characters before and after a hyphen.random-item(),ident()andinherit()functions.
The release also lists many fixes, including how var() behaves as an arbitrary substitution function and how attr() invalidates when attributes change.
Takeaway
CSS is gaining real logic: functions with typed parameters, inline conditions and typed attributes. Start small. Replace one Sass helper with an @function, collapse a set of modifier classes with if(), or drive a grid from a data- attribute. Keep a plain fallback above each modern declaration, and you can use them today while browser support fills in.
Source: Safari 27.3 beta release notes.
