Skip to content

[css-shapes-2][css-borders-4] Add a way to change an element's shape #6997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
SebastianZ opened this issue Jan 29, 2022 · 57 comments · Fixed by #11656
Closed

[css-shapes-2][css-borders-4] Add a way to change an element's shape #6997

SebastianZ opened this issue Jan 29, 2022 · 57 comments · Fixed by #11656

Comments

@SebastianZ
Copy link
Contributor

@LeaVerou mentioned an element-shape property in #6980 (comment) as a way to influence the shape of an element. The idea behind that property is to change the shape of the element so that border, shadows, etc. follow that shape.

So far we have the shape-outside property which influences the float area. I suggest to extend this property to be able to specify that it not only affects the float area but also all the other boxes created for the element.

Sebastian

@jsnkuhn
Copy link

jsnkuhn commented Jan 31, 2022

this issue about trying to get borders etc to play nice with clip-path seems related: #5881

@bradkemper
Copy link
Contributor

bradkemper commented Jan 31, 2022

I think we should either extend shape-outside or clip-path or both. Probably clip-path would make more sense, since it is already creating a background shape, and we want it to also create a border shape. Whether it is floated via shape-outside is related, but maybe less relevant.

I was thinking it could be a keyword we add to those properties, as in clip-path: circle(50%) border-shape. When that keyword is added, it would mean that you would get the border-top value and use it to stroke the inside of the shape and making the padding box that much smaller if it has box-sizing: border-box, or stroking the outside of the shape if it has box-sizing: content-box. The stroke would affect layout, much like a regular border. The border would not be clipped by its own clip-path. The normal rectangular border would not be added. We would in effect be creating a shaped border-box that is used for box-shadow and filters. Those filters and box-shadows would not be applied to the normal rectangular border-box, just to the stroked shape.

@bradkemper
Copy link
Contributor

Also, add clip-path as a keyword value of shape-outside, so that shapes only have to be defined once, in the clip-path property, and used to also define the path of shape-outside.

@faceless2
Copy link

Big +1 to Brad's comments. We have shape-outside and clip-path already, they should be reused rather than adding another definition of the shape-of-a box. The clip-path value to shape-outside is also a good (and probably overdue) idea.

@jsnkuhn
Copy link

jsnkuhn commented Jun 4, 2022

with the reuse of a clip-path like syntax in mind clip-path already has an inset() option right now that allows for a round option similar to border radius. So it seems like things are already primed to be extended for corner-shape

clip-path: inset(20px round 10px)

does fell a bit strange hiding the corner-shape-ing just inside inset() though.

wonder if something like corners(angle round round 50%); could be used to make the corners thing less of an after thought here.

@jsnkuhn
Copy link

jsnkuhn commented Jun 28, 2022

would it theoretically in anyway be feasible to get border-image to follow an element-shape path? Like down the left side of this text box?

border-image-shape

@SebastianZ
Copy link
Contributor Author

with the reuse of a clip-path like syntax in mind clip-path already has an inset() option right now that allows for a round option similar to border radius. So it seems like things are already primed to be extended for corner-shape

Any solution found here needs to define how to deal with border-radius and corner-shape.

If we end up with extending clip-path for that, we might close this issue in favor of #5881. But we'll see.

Sebastian

@SebastianZ
Copy link
Contributor Author

Btw. just referencing #2180 in regard of the clip-path value for shape-outside.

Sebastian

@jsnkuhn
Copy link

jsnkuhn commented Jun 30, 2022

I don't really think giving clip-path the ability to have borders/box-shadows etc is the right way to go here. clip-path is already doing what it's intended to do i.e clip a path. It makes more sense to for there to be a separate property to deal with the creation of an element shape.

There are plenty of existing use cases of having a rectangular shape that is revealed with a clip-path animation. No reason folks wouldn't want to do the same thing with non-rectangular shapes. If you are already using clip-path for the elements shape how would it be possible to reveal the element with a clip-path animation?

GIF

https://codepen.io/jsnkuhn/pen/BaryjEV?editors=1100

@jsnkuhn
Copy link

jsnkuhn commented Aug 29, 2022

Ran across this page with lots of interestingly shaped elements: https://atlus.com/persona5/home.html

image

@smfr
Copy link
Contributor

smfr commented Jul 30, 2024

shape-outside is about wrapping content around an element. clip-path is about clipping, and does not affect border drawing.

I think the solution here is a property that allows the author to specify a shape that affects the painted border, like border-shape or element-shape. The author might want to specify a single path which is stroked to generate the border, or two paths which form the inner and outer edge of the painted border (i.e. a path with a hole), in which case the space between them is filled with the border color(s).

@noamr
Copy link
Collaborator

noamr commented Aug 2, 2024

I think this is a duplicate of #5881 in some ways. We should perhaps discuss them as one thing?

@smfr
Copy link
Contributor

smfr commented Aug 27, 2024

Here's a very rough proposal for a border-shape syntax, with the intent of having border-shape encompass corner-shape and the desire to have user-defined element shapes.

<corner-shape> = [round | bevel | scoop | notch]{1,4}
<border-line-style> = dotted | dashed | solid
<border-radius> = <length-percentage [0,∞]>{1,4} [ / <length-percentage [0,∞]>{1,4} ]?

border-shape: (<corner-shape> <border-radius>?)
              || (<basic-shape> <line-width> <border-line-style>? <geometry-box>?)
              || (<basic-shape> <geometry-box>? <basic-shape> <geometry-box>?)

If the <corner-shape> version is specified, this can be used to specify the corner treatment for each corner, and optionally border radii that override border-radius values. The appearance of corner shapes is influenced by border widths, as with border-radius currently (note this might be non-trivial for some corner shapes).

If the single <basic-shape> variant is specified, this provides a path, with the given stroke width, which is rendered instead of the border[1]. The path is resolved relative to the given <geometry-box>, defaulting to border-box. Colors are taken from the border-color property (different colors on each side are rendered with corner joins, as they are now with border-radius). The border is rendered by stroking the path, centered on the path (insetting an arbitrary path involves non-trivial compute, and masking to get an inset path doesn't work for paths with loops). Dotted and dashed paths are supported. (Double borders could also be supported by masking and rendering the path a second time for the center slot with clearing.) A future property could allow control over the dash origin.

If the double <basic-shape> variant is specified, the first shape is used as the outside of the border shape, and the second shape as the inside. The border fill is painted in the space between these two paths. Each path is resolved relative to the given <geometry-box>.

border-clip: border-area fill the border shape.

Box shadows follow the outer and inner border shapes as appropriate.

border-shape does not affect the box geometry, which is still computed using the widths specified via border-width. Areas of the shape that project outside the border-box are considered as ink overflow. Areas of the inner shape that project outside of the border-box will be considered part of the background area, so filled with backgrounds (we probably need a new value for background-clip to allow for this).

border-shape does not affect the flow of content inside the box.

The clipping of content behaves as it does for border-radius: the inside shape of the border clips content, if overflow is non-visible.

The shape variants of border-shape have no effect in cases where it's necessary to clip to the content box (replaced elements?). Currently border-radius does affect content box clipping in some cases, but it's not possible to easily inset arbitrary shapes to compute content shape clipping.

[1] Maybe the rectangular border also renders, and the author has to set the color to transparent to hide it? The shape would then be a way to just extend the background.

Here's a screenshot for some in-progress experiments in WebKit:

border-shape
which is a rendering of

            padding: 40px;
            overflow: hidden;
            border: 60px solid transparent;
            box-shadow: 0 0 20px black, 0 0 20px black inset;
            border-shape:
                shape(from 0% 0%,
                    hline to 100%,
                    vline to 75%,
                    smooth by -8.295% 0% using -3.7% -2.2%,
                    smooth by -8.5% 12.8% using -4.1% 10.7%,
                    smooth by -7.9% -3.2% using -3.7% -5.2%,
                    smooth by -7.3% 9.3% using -2.7% 7.1%,
                    smooth by -9.0% 0% using -4.1% -2.3%,
                    smooth to 0.82% 100% using 0.82% 100%,
                    close) border-box,
                shape(from 10% 10%,
                    hline to 90%,
                    vline to 64.621%,
                    smooth by -8.295% 0% using -3.7% -2.2%,
                    smooth by -8.5% 12.8% using -4.1% 10.7%,
                    smooth by -7.9% -3.2% using -3.7% -5.2%,
                    smooth by -7.3% 9.3% using -2.7% 7.1%,
                    smooth by -9.0% 0% using -4.1% -2.3%,
                    smooth to 10% 90% using 10% 90%,
                    close) border-box;            
            background-image: linear-gradient(to bottom right, orange, blue, green), none;
            background-origin: border-box;
            background-clip: border-area, border-box;

@noamr
Copy link
Collaborator

noamr commented Aug 27, 2024

Here's a very rough proposal for a border-shape syntax, with the intent of having border-shape encompass corner-shape and the desire to have user-defined element shapes.

I'm sure other people would have more to say about the specifics, but my initial reaction is "let's do it!"

@smfr
Copy link
Contributor

smfr commented Aug 27, 2024

[1] Maybe the rectangular border also renders, and the author has to set the color to transparent to hide it? The shape would then be a way to just extend the background.

On second thoughts, since the stroke-based and area-based shapes take their colors from border-color, the normal rectangular borders should not be rendered.

@smfr smfr added the Agenda+ label Aug 27, 2024
@noamr
Copy link
Collaborator

noamr commented Aug 27, 2024

@smfr what does the two-shape syntax add on top of using a single shape with close and evenodd?

.shapre {
            padding: 40px;
            overflow: hidden;
            border: 60px solid transparent;
            box-shadow: 0 0 20px black, 0 0 20px black inset;
            border-shape:
                shape(from 0% 0% evenodd,
                    hline to 100%,
                    vline to 75%,
                    smooth by -8.295% 0% using -3.7% -2.2%,
                    smooth by -8.5% 12.8% using -4.1% 10.7%,
                    smooth by -7.9% -3.2% using -3.7% -5.2%,
                    smooth by -7.3% 9.3% using -2.7% 7.1%,
                    smooth by -9.0% 0% using -4.1% -2.3%,
                    smooth to 0.82% 100% using 0.82% 100%,
                    close,
                    move to 10% 10%,
                    hline to 90%,
                    vline to 64.621%,
                    smooth by -8.295% 0% using -3.7% -2.2%,
                    smooth by -8.5% 12.8% using -4.1% 10.7%,
                    smooth by -7.9% -3.2% using -3.7% -5.2%,
                    smooth by -7.3% 9.3% using -2.7% 7.1%,
                    smooth by -9.0% 0% using -4.1% -2.3%,
                    smooth to 10% 90% using 10% 90%,
                    close) border-box;            
            background-image: linear-gradient(to bottom right, orange, blue, green), none;
            background-origin: border-box;
            background-clip: border-area, border-box;
            }
            ```

@smfr
Copy link
Contributor

smfr commented Aug 27, 2024

The single-shape syntax is a request to stroke the given path (which may contain multiple sub-paths). The two-stroke syntax is a request to fill the area between the paths. It's possible we should switch between these behaviors with a keyword, but then you might have an author providing a single-path path and requesting a fill, which would be an error (I don't think we ever want the border to act as a fill over the entire element).

@noamr
Copy link
Collaborator

noamr commented Aug 27, 2024

The single-shape syntax is a request to stroke the given path (which may contain multiple sub-paths). The two-stroke syntax is a request to fill the area between the paths. It's possible we should switch between these behaviors with a keyword, but then you might have an author providing a single-path path and requesting a fill, which would be an error (I don't think we ever want the border to act as a fill over the entire element).

Maybe I'm still getting this wrong, but is this equivalent to using a different winding-rule for fill and stroke? evenodd for fill and nonzero for stroke?

@smfr
Copy link
Contributor

smfr commented Aug 27, 2024

I don't think so. Authors might want to specify different winding rules for the two-path form. It's about wanting the path to be stroked, vs. wanting the path to be filled. But maybe we should also have the two-path form allow stroking as well?

@yisibl
Copy link
Contributor

yisibl commented Aug 29, 2024

@smfr For the example above, I'm curious, how would adding border-bottom-right-radius render?

@jsnkuhn
Copy link

jsnkuhn commented Aug 29, 2024

Just for confirmation we are changing the entire shape of the element here correct?

If so shouldn't the property name be "element-shape" in seat of "border-shape"? The border is only that shape because it traces/strokes the element's shape. It also seems that a "border"/stroke would not be required and therefore calling it "border-shape" could be confusing. A border-less "border-shape" just seems odd to me. If you have a border-less element-shape the name still makes sense.

@noamr
Copy link
Collaborator

noamr commented Aug 29, 2024

Just for confirmation we are changing the entire shape of the element here correct?

If so shouldn't the property name be "element-shape" in seat of "border-shape"? The border is only that shape because it traces/strokes the element's shape. It also seems that a "border"/stroke would not be required and therefore calling it "border-shape" could be confusing. A border-less "border-shape" just seems odd to me. If you have a border-less element-shape the name still makes sense.

It's consistent with border-radius, and has very similar semantics.

@smfr
Copy link
Contributor

smfr commented Sep 30, 2024

The current proposal doesn't have a way to just get that border on the left edge (with rounded ends), though.

@noamr noamr changed the title [css-shapes-2] Add a way to change an element's shape [css-shapes-2][css-borders-4] Add a way to change an element's shape Oct 1, 2024
@tabatkins
Copy link
Member

So over the break @tabatkins and I came up with an algorithm for how to not only draw the border on a single shape, but even respect the different top, right, bottom, left border widths.

A little more detail on our motivation:

It's reasonable to view border-shape as providing three levels of increasing complexity & power for specifying borders: the border-* properties define a rounded rect path that gets stroked; the single-path version defines an arbitrary path that gets stroked; the two-path version defines an Iarbitrary shape that gets filled. Generally, we want increasing complexity to track with increased power, so authors that want only a small deviation from the built-in stuff can just go up a small amount in complexity, rather than having to jump all the way to some "primitive" version that's super hard to work with (but super powerful).

So, the "rounded-rect" borders can have up to four distinct widths and colors, one for each horizontal/vertical side, with the rounded corners smoothly interpolating between the values. Our proposal is, we think, both the simplest and most natural way to reproduce this ability in the single-path syntax, with the path direction dictating which of the four border-width/color values to draw from (or which two to interpolate between, and by how much).

If you use the single-path to produce a rounded rect, or any reasonably close variation on that, you'll get a result essentially identical to what is defined for the border-* properties today. (The way the corners are interpolated will be slightly different, which I think is unavoidable - I don't think the corner interpolation rules can be applied to an arbitrary path without an explosion of complexity. But I could be wrong about that.)

If you do other shapes, you'll still get a reasonable result, with decent control knobs to twiddle. If this still isn't enough, the two-path syntax still exists, but we think this should provide enough power to the single-path syntax that you'll often be satisfied.


We could also provide a further intermediate syntax, where you can provide the colors and widths along the path explicitly, tied to the path distance; similar to defining gradient stops. We're already using that syntax in gradients and in linear(), and will likely use it in more places (cubic splines for more easing?), so it is a familiar pattern to work with. This is more complex than needed for simple shapes, but is still vastly less complicated than the two-path syntax.

@tabatkins
Copy link
Member

tabatkins commented Oct 1, 2024

Here's my fuller proposal, in more detail:

  • By default, the single-path syntax determines its stroke width and color by consulting the border-width and border-color properties. Its width and color at any point depends on the path direction: directly to the right takes from border-top-*, directly down takes from border-right-*, etc; between those angles it linearly interpolates the width and color.
  • Possibly, you can override this by specifying a single specific width and color in the syntax.
  • Optionally, you can provide a gradient-stop-like list of widths and colors, each associated with a path distance (0% indicating the start, and 100% indicating the end, or lengths can be used). This overrides width/color behavior from the border-* properties. Possibly we just let this completely handle the previous bullet's case, since you can just write a single stop and get that behavior.

@noamr
Copy link
Collaborator

noamr commented Oct 2, 2024

A couple of questions/clarifications:

  1. Wouldn't we want the same inner/outer functionality for the border-radius/corner-radius case? Like have rounded corners of different radiuses outside and inside? Feels a bit arbitrary that this simple rounded/smoothes version is only a one-shape syntax and the full basic-shape syntax doesn't include it. Perhaps we could utilize outline for this, where the inner shape strokes based on outline values and the outer shape strokes based on border values? In any case the rectangular outline makes little sense here.

  2. It's very surprising for me that background is what fills between the shapes. I would expect backgrounds to fill the inside shape, like in border-radius. I think we should find some semantic that works with border-image and aligns with Tab's proposal above for how the different border-widths work.

@smfr
Copy link
Contributor

smfr commented Oct 2, 2024

I understand the motivation for the single path syntax taking its widths from border-width, but I think in practice this is going to be pretty hard to implement. The proposal to inset the path by taking a perpendicular line at each point whose length is an interpolated width is interesting, but may result in a generated path with thousands of points; both the computation and rendering could be slow. Perhaps there's a way to do something similar with a scale transform which just scales the path, using a center point that takes the border widths into account?

Optionally, you can provide a gradient-stop-like list of widths and colors, each associated with a path distance (0% indicating the start, and 100% indicating the end, or lengths can be used)

I don't think this will work very well if you apply it to boxes with different aspect ratios; the author would have a hard time matching gradient points with the box corners.

It's very surprising for me that background is what fills between the shapes

I'm not sure it does? I think we can fill the space between the inner and outer shapes with border colors by default, with the usual diagonal corner joins.

@noamr
Copy link
Collaborator

noamr commented Oct 2, 2024

It's very surprising for me that background is what fills between the shapes

I'm not sure it does? I think we can fill the space between the inner and outer shapes with border colors by default, with the usual diagonal corner joins.

I guess that's what I understood from #6997 (comment)?

@smfr
Copy link
Contributor

smfr commented Oct 2, 2024

I guess that's what I understood from #6997 (comment)?

That was with border-clip: border-area. Using border colors, you'd get:

border-colors

@tabatkins
Copy link
Member

tabatkins commented Oct 2, 2024

I understand the motivation for the single path syntax taking its widths from border-width, but I think in practice this is going to be pretty hard to implement.

Is this a limitation of CoreGraphics or a more general issue? To my knowledge, Skia is okay with variable-width strokes (@fserb ?).

(I think it's okay if we end up doing something simpler by default, fwiw, so long as it's possible to do variable widths/colors in the 1-path syntax somehow in a reasonably usable manner. Like, worst case, we could just take the border-top width and color, or something like that.)

I don't think this will work very well if you apply it to boxes with different aspect ratios; the author would have a hard time matching gradient points with the box corners.

Yeah, this is definitely a special-case "one specific box" sort of thing, which doesn't make me very happy. We need something to control this, tho; even if you fall back to the two-path syntax, you'd then need to do a one-off fill image, which is just as wonky.

The only other suggestion I would have is being able to match up the widths/colors with the path commands (or rather, between the commands), either with a separate list that has a 1:1 correspondence, or inserting them directly into the shape() syntax. I lean toward the latter; we can just say that contexts that aren't stroking the path would ignore that information (or if they're stroking it but doing a stroke fill some other way, they'd ignore the color info).

That way, you could reproduce a rounded-rect with 8 path commands, as normal, and just place the desired border-width/color before/after each straight-line segment; the corner arcs would interpolate. Or, of course, do something else, like in this example (where the angled corners take the color of the left/right sides, rather than being half-colored by each side).

We already might want to put in corner-rounding controls into shape() between commands (rather than always requiring them to be communicated separately, and applying equally to all corners), so I think this fits in quite naturally.

Using border colors, you'd get:

What's determining the angle/position of the color change there? It can't just be radial lines from the center, as that would mean the three sharp corners would only look right if the border-width ratios happened to match the box's own aspect ratio.

@tabatkins
Copy link
Member

tabatkins commented Oct 2, 2024

Or, another alternative: define another function, stroke(), which is identical to shape() but specifically defines a stroked path. It can then have the additional grammar items that handle stroke width and color, along with future items in a prelude argument like stroke direction (center/left/right) and such. (And it would not have the fill-specific items in its prelude argument, like the nonzero/evenodd keyword that shape() has.)

Then, individual contexts would define whether they expect a <shape> or a <stroke>. If you pass stroke() to a <shape>, it's allowed, but we ignore all the stroke-specific stuff and default the fill-specific details in a context-specific manner. If you pass shape() (or any of the basic-shape function) to a <stroke>, it's allowed, but we ignore all the fill-specific stuff and default the stroke-specific details in a context-specific manner (such as getting the stroke width from 'border-top-width' or something). (We already do this if you use path() or shape() in Motion Path, for instance; the nonzero/evenodd keyword is simply ignored, since that spec actually takes a third semantic type, a plain ol' path.)

So the one-path syntax would actually be a <stroke>; the two-path syntax would be a pair of <basic-shape>s.

@smfr
Copy link
Contributor

smfr commented Oct 2, 2024

What's determining the angle/position of the color change there? It can't just be radial lines from the center, as that would mean the three sharp corners would only look right if the border-width ratios happened to match the box's own aspect ratio.

Exactly the same as the color joins with border-radius and different border widths; I think the joins are the connecting lines between the corners of the border box and the padding box.

@tabatkins
Copy link
Member

I'm still not sure how that works - the four corners can all have completely different angles, and lines extended from them aren't guaranteed to meet in a single point (in fact, they usually won't). Do you, like, find their first intersection points, then connect those points with a line, to divvy up the interior into four zones (some of which might be trapezoidal), and then fill the border shape accordingly?

@noamr
Copy link
Collaborator

noamr commented Jan 27, 2025

I'm still not sure how that works - the four corners can all have completely different angles, and lines extended from them aren't guaranteed to meet in a single point (in fact, they usually won't). Do you, like, find their first intersection points, then connect those points with a line, to divvy up the interior into four zones (some of which might be trapezoidal), and then fill the border shape accordingly?

That's what seems the most simple and logical to me to do with varying border-color and border-width. It's a lot simpler than trying to follow the perpendiculars, and would produce a much more coherent result when you have different color+width pairs. Perhaps we should start with that and check if we can allow more complex control later on?

@noamr
Copy link
Collaborator

noamr commented Jan 28, 2025

Some thought about how border-shape works with border-width (hopefully we'll have time to discuss it at the F2F):
It would be confusing in some scenarios if border-width affected both the box model and the stroke of a shape that might not be boxy at all. An author might change the border-width in order to change the stroke, to find out that it moves the child elements because the padding box had now moved...

This is an issue with border-shape being purely decorative on one hand, but respecting layout properties for its style on the other hand.

I wonder if border-shape should have a stroke in addition to the shape(s), which could have the border-width as a default. Same for fill and border-color.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-shapes-2][css-borders-4] Add a way to change an element's shape, and agreed to the following:

  • RESOLVED: corner-shape and border-shape are independent properties
  • RESOLVED: border-shape overrides corner-shape / border-radius
  • RESOLVED: border-shape is a non-layout affecting property
  • RESOLVED: default stroke width / color get taken from existing border props, specifics TBD
The full IRC log of that discussion <emilio> noamr: I can give a bit of a recap
<emilio> ... we resolved at TPAC on having a draft for border-shape
<emilio> ... I started working on that and discovered some questions
<emilio> ... one of them is how it relates to corner-shape
<emilio> ... other one is how it works with border-width etc
<emilio> ... so wanted smfr to demo what he has
<emilio> ... and then talk demos
<emilio> smfr: [shows prototype]
<emilio> ... uses border-shape with the recent `shape()` function
<emilio> ... so it effectively takes over the rendering of the rounded rect
<emilio> ... and replace it with effectively two shapes
<emilio> ... one for the inside of the rect, one for the outside
<emilio> ... actually first outside, second inside
<emilio> ... it uses two different beziers, and it shows background-clip: border-area, and also box-shadow which both follow those shapes
<emilio> ... the way I think about this is replacing the rendering of the radius
<emilio> s/radius/border/
<emilio> ... it follows the same rules regarding clipping of the content etc, just like border-radius
<emilio> ... that's the two-shape version of this
<emilio> ... [switches demo]
<emilio> ... this one is the one-shape version, with a hole
<noamr> q+
<emilio> ... I think I used border-top-width for the line width
<emilio> ... but single-shape has issues re. that interaction
<emilio> ... a separate draft of mine just specifies effectively a stroke width
<emilio> ... interpolating seems hard
<emilio> ... also this is purely decorative. Affects clipping and ink overflow, but layout still uses the general box model
<emilio> q+
<emilio> ... what that means is that you might specify shape that allow the inside edge to project outside of the border-box
<emilio> ... which exposes things like extra background area or what not which are a bit weird
<TabAtkins> q+
<astearns> ack noamr
<emilio> ... one other thing I wanted to mention the connection between this and corner-shape is that this breaks assumptions
<emilio> noamr: one of the oddities is how it interacts with border-width
<emilio> ... with border-radius it kinda works
<emilio> ... so that's one oddity of this
<emilio> ... if your shape is kinda boxy you probably want your border-width to affect it
<emilio> ... I think border-width would be a default you can override
<emilio> ... same with border-color
<emilio> ... my thinking was that this could behave like a mask going through the borders
<noamr> https://github.com//issues/6997#issuecomment-2389134315
<emilio> ... would be a simple solution but not what people would want
<fserb> q+
<emilio> ... I think this should be separate from corner-shape
<emilio> ... we should restrict corner-shape to rectangles
<emilio> ... where we can use it with border-radius
<emilio> ... and this would smooth your radius rather than something that controls shape
<emilio> ... would be good to separate it at least in the beginning
<emilio> ... and we'd leave the interaction problems for later
<bramus> emilio: i was wwondering if this corrected border matches stuff how border-image works today
<bramus> … not sure how border-image and border-radius interact
<bramus> … feels similar
<bramus> … in the sense that b-i disregards b-w and layout
<bramus> … would be good to be consistent witht hat
<florian> q+
<astearns> ack emilio
<bramus> … can find some answers to the weird questions here
<astearns> ack TabAtkins
<emilio> TabAtkins: generally agree with emilio, border-image does use border-width for the area but you can override
<emilio> ... we should approach this to keep this simple, the geometry should be overridden by shape, which is what simon is suggesting as well
<emilio> ... supportive of not paying attention to color or width as much as possible
<emilio> ... had some suggestions but not fully fledges
<emilio> ... having some way of specifying variable colors along the path seems nicer than trying to interpolate based on existing border properties
<schenney> +1 to Tab's color/width idea.
<schenney> Interpolate between points.
<emilio> smfr: sounds hard, not sure if svg has a way of changing colors along the path
<emilio> ... nice suggestion
<emilio> TabAtkins: what about variable width stroke?
<emilio> smfr: you need to use multiple paths for that
<emilio> TabAtkins: that's kind of annoying
<emilio> ... quite a jump from border-radius to some two-path
<schenney> q+
<emilio> weinig: PostScript model is a bit old, updating it might be reasonable
<RRSAgent> I have made the request to generate https://www.w3.org/2025/01/29-css-minutes.html fantasai
<emilio> ... sticking just to pdfs had in late 90s got us pretty hard
<emilio> ... pushing that seems reasonable
<astearns> s/hard/far/
<emilio> ... we can build the paths and draw new stuff
<emilio> ... would just be slow
<emilio> ... doesn't mean that we need to be constrained, we should be able to push that
<emilio> ... we know how to do the conversion from border-radius to multiple paths
<emilio> TabAtkins: I agree that as long as it's not unusably slow we should be able to do this, authors would just have to wait for animation use cases
<emilio> fserb: two things
<astearns> ack fserb
<emilio> ... first, I think I tend to agree with Tab that it might be possible to given one shape and automatically build the two things
<emilio> ... it is probably possible
<emilio> ... need to be careful about where it begins
<emilio> ... I don't think we need more infrastructure from low level graphics APIs for this
<emilio> ... if we have a good way of determining how the inner shape works from the outer and a width
<emilio> smfr: I think some shapes might be pretty hard, like shapes with loops and what not
<emilio> fserb: I think that works less for colors than for width
<emilio> ... I don't know what colors mean on a shap
<emilio> ... I think leaverou wanted to do quadrants, but even that seems a bit sketchy
<TabAtkins> (i'm fine with abandoning gradient stroke)
<emilio> ... but would be nice to try this for border
<emilio> ... easy way is to lose some of those other properties, but maybe we regret this
<noamr> q+
<emilio> ... Maybe it's worth trying to connect border-width to this, even if it's probably hard
<astearns> ack florian
<fantasai> +1 fserb
<emilio> florian: back to an earlier point, the fact that this would be disconnected from layout border-width
<emilio> ... it might be seen as a feature
<emilio> ... controlling the layout so taht it doesn't interact with the border seems useful
<emilio> ... so defaulting stroke to top
<TabAtkins> yeah, taking the default width/color from border-top seems fine
<emilio> ... and using left/right/bottom for layout adjustments seems nice
<astearns> ack schenney
<emilio> ... maybe weird but convenient
<fantasai> or average all the colors? :)
<emilio> schenney: so in your original demo, where would scrollbars be if you had overflow: scroll?
<TabAtkins> scrollbar-path: shape(...)
<emilio> noamr: The problem with scrollbars is the same for border-radius, it appears on top
<emilio> ... ugly, but needed for a11y
<florian> scrollbar-path ?
<emilio> ... this conversation about the border stuff being this default makes sense
<emilio> schenney: so we're leaning towards making this not affect the layout right?
<emilio> correct
<emilio> smfr: Firefox applies extra constraints to border radius for scrollbars
<emilio> ... maybe the spec could allow that for border-shape too
<astearns> ack noamr
<emilio> noamr: wanted to go back to separate it from corner-shape
<emilio> ... so keep corner-shape for boxes, and border-shape for other stuff
<noamr> q+
<florian> q+
<emilio> smfr: so do we want to merge corner-shape and border-shape or keeping separate?
<TabAtkins> would prefer separate
<astearns> ack noamr
<fserb> +1 separate
<TabAtkins> q+
<emilio> noamr: one argument against combining is that corner-shape interacts with border-radius
<astearns> ack florian
<emilio> smfr: you could also see a border-shape that's mostly rounded as an enhancement on border-radius
<emilio> florian: so border-shape: auto uses the rest, and otherwise you override
<ydaniv> q+
<emilio> emilio: but you have two props at that point already
<emilio> q+
<bramus> emilio: dont think that is exclusive
<bramus> … if you keep the on the same property you can still define sth like that
<bramus> … if corner shape is superellipse then you account for border radius and take over
<bramus> florian: could be worng that corner shape thing has not 1 value but 1, 2,4 …
<bramus> … it has longhands to go along with this
<bramus> … whcih border-shape does not
<bramus> … its 4 props (or more) rolled into 1
<bramus> … where border-shape is 1 thing
<bramus> emilio: that’[s a good argument
<astearns> ack TabAtkins
<emilio> TabAtkins: pretty strong to keep them separate
<emilio> ... as it was brought up, generic paths have a lot of complexity
<emilio> ... that prevents us from making assumptions
<emilio> ... well defined corner shapes don't have those constraints, and we can use border-width etc
<emilio> ... so I think corner-shape should be on the general thing, and the path version should do its own thing on top
<astearns> ack ydaniv
<emilio> ydaniv: we could consider reversing the names, conceptually
<emilio> ... having a shape as the shorthand and border / corner as longhands
<emilio> ... maybe think how they interact
<emilio> ... may make it more complicated tho
<astearns> ack emilio
<emilio> astearns: so leaning about separate properties?
<emilio> smfr: yeah, modulo bikeshedding name
<emilio> noamr: yeah I think last bikeshed was border-shape, but we can always change it in the future
<emilio> border-shape: <shape> <shape>?; + strokes TBD (top border width default + override)
<astearns> ack fantasai
<emilio> fantasai: I agree we should have border-{width,color} in
<emilio> ... I think the border shorthand... ?
<emilio> ... what's the plan for corner-shape?
<emilio> fantasai: for color we might want the border-top-left color, or block-start, or averaging all the four colors
<emilio> ... all slightly awkward
<TabAtkins> block-start, definitely. i don't think averaging will produce anything meaningful. ^_^
<emilio> astearns: maybe separate issue?
<fantasai> s|colors|colors/widths|
<emilio> noamr: details I need is (1) corner-shape is different
<emilio> PROPOSED: corner-shape and border-shape are independent properties
<fantasai> none sgtm
<emilio> PROPOSED: border-shape overrides corner-shape/border-radius and co
<fserb> sgtm
<emilio> RESOLVED: corner-shape and border-shape are independent properties
<emilio> ack fantasai
<astearns> ack fantasai
<emilio> fantasai: I think necessarily one has to override the other, and border-shape needs to override the other
<emilio> ... we could create a shorthand for both border and corner shape
<emilio> ... I don't see how we could have both applying at the same time
<noamr> that shorthand would have to also have border-radius
<fantasai> yes
<bramus> emilio: consensus is that you cant have both appplied at th esame time and you cannot make them be the same property if you want corner-shape to be a shorthand
<bramus> florian: yes, and we could have a super shorthand but can discuss separately
<bramus> fantasai: yes, what i am saying
<bramus> emilio: should also discuss things like them being reset or not by the shorthand
<bramus> astearns: a super shorthand sound scary :)
<emilio> RESOLVED: border-shape overrides corner-shape / border-radius
<bramus> emilio: should we resolve on it not affecting layout too?
<bramus> astearns: yes
<bramus> PROPOSED RESOLUTION: border-shape is a non-layout affecting property
<bramus> RESOLVED: border-shape is a non-layout affecting property
<emilio> PROPOSED: border-shape doesn't affect layout, only ink overflow and graphic effects like clipping and shadows
<bramus> astearns: other things to discuss?
<bramus> noamr: the derfault stroke and color are taken from the existing border props. can say tbd which property exactly
<emilio> PRPOSED: default stroke width / color get taken from existing border props
<emilio> which ones TBD
<emilio> smfr: maybe TBD is ok but I think it's fine to do the diagonal color joint in the issue
<emilio> RESOLVED: default stroke width / color get taken from existing border props, specifics TBD

@fserb
Copy link
Member

fserb commented Jan 29, 2025

One proposal for border-width interaction.

Break it down in two steps.

Step 1. Assume a width(t) function that returns the width of the border parametrized over its length (t=0 begin of the path,, t=1 end of a continuous path). For each path, the width(t) function can be applied to create an outer shape by moving the path outside in the direction of the normal of the path.

Step 2. How to build width(t). One option is to allow the user to specify a function or 1d gradient directly for each continuous path. But let's ignore that for now. Can we build a reasonable function given border-width? I think so. We can define "control points" (similar to what we have today): 0 - 0.25 - 0.5 - 0.75 - 1.0. Each interval gets one width. And then we specify a transition range: so, something like: width(t) = border-top-width [0.05, 0.2], lerp(top, right) [0.2, 0.3], right [0.3, 0.45] ... etc. We can decide how much control/flexibility to give. This function would be applied to every continuous path on the shape.

Would something like this work?

@noamr
Copy link
Collaborator

noamr commented Jan 29, 2025

One proposal for border-width interaction.

Break it down in two steps.

Step 1. Assume a width(t) function that returns the width of the border parametrized over its length (t=0 begin of the path,, t=1 end of a continuous path). For each path, the width(t) function can be applied to create an outer shape by moving the path outside in the direction of the normal of the path.

Step 2. How to build width(t). One option is to allow the user to specify a function or 1d gradient directly for each continuous path. But let's ignore that for now. Can we build a reasonable function given border-width? I think so. We can define "control points" (similar to what we have today): 0 - 0.25 - 0.5 - 0.75 - 1.0. Each interval gets one width. And then we specify a transition range: so, something like: width(t) = border-top-width [0.05, 0.2], lerp(top, right) [0.2, 0.3], right [0.3, 0.45] ... etc. We can decide how much control/flexibility to give. This function would be applied to every continuous path on the shape.

Would something like this work?

I'm going to open separate issues for this once the draft is in. Let's add these ideas there

noamr added a commit to noamr/csswg-drafts that referenced this issue Feb 5, 2025
Closes w3c#6997

This defines the 'border-shape' property with a narrow set of details
that we've resolved on, leaving room for discussing some details as we
go along.

Specifically, this adds the two variants of 'border-shape'
(single/double <basic-shape>).

Issues to be opened separately:
* interaction with border-{width|color|style}
* Clipping replaced elements
* Lots of examples
@noamr noamr closed this as completed in 584704e Feb 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Friday morning
Status: Wednesday morning
Development

Successfully merging a pull request may close this issue.