Closed bwklein closed 3 years ago
+1 - similar situation - html page laid out with a button within an article - prose completely overrides any styling on the button. I was looking for a no-prose/prose-off/plain utiity.
+1 I would like to be able to deactivate prose on an element basis as well, but we also need to take a step back and look at it from the greater perspective: The basic principle of tailwind CSS is the utility based classes, so it would only make sense to keep the approach in mind while developing plugins. Therefore a utility class should always override a plugin/theme like prose, otherwise it's not a utility class.
Example: If I want a h1 to be text-green-500 for example, it should override the color provided by the prose collection. Right now this does not work.
Let me know what you think :)
Therefore a utility class should always override a plugin/theme like prose, otherwise it's not a utility class.
To do this you just need to use the important
feature in Tailwind. We have no ability to control how specificity works in CSS in any other way, just how CSS works.
@bwklein Definitely agree it would be nice to have some way to "escape" from the prose styling. We've done it many times on our own projects (never loved our solutions though) so it's definitely something we'd like to add.
Meaning that I can override the prose class by using the important feature then the utility class I wish to apply?
On 21 Jul 2020, at 15:55, Adam Wathan notifications@github.com wrote:
Therefore a utility class should always override a plugin/theme like prose, otherwise it's not a utility class.
To do this you just need to use the important feature in Tailwind. We have no ability to control how specificity works in CSS in any other way, just how CSS works.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/tailwindcss/typography/issues/32#issuecomment-661876576, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA5KSZ4PF4XHYNNHG27ZXPDR4WM57ANCNFSM4PC3XMEA.
I solved this by using the most generic important
feature in my configuration.
module.exports = {
important: "html",
plugins: [
require("@tailwindcss/typography"),
],
};
As a (potentially bad) suggestion, I think this can be accomplished using the :not
selector. I have tested it locally, and it works where I needed it, removing the underline on anchor tags. in style.js
- a: {
+ '*:not(.no-prose) a:not(.no-prose)': {
color: defaultTheme.colors.gray[900],
textDecoration: 'underline',
},
This seems like it might be bad from a performance perspective, but I'm not sure the best way to actually test this.
This will disable 'prose' for anchor tag if itself, or any parent has the no-prose
class.
For me I have GIST script embed links in my markdown, when it renders the prose styling kinda messes it up a bit. But yeah the ability to turn it off in places would be super handy.
I tried doing this:
typography: {
default: {
css: {
pre: {},
}
}
}
I was not surprised to see that it didn't work 😅 maybe we could do pre: false
to disable any .prose pre
styling?
Edit: it looks like this does work:
typography: {
default: {
css: {
pre: false,
code: false,
'pre code': false,
'code::before': false,
'code::after': false
}
}
}
@JasonEtco agree with this. I need to disable the default styling of ul and ol due to conflicts with wordpress!
@tomphilpotts You can do that already by following the example in @JasonEtco's comment 👍
Yes, this would be nice. Ran into this issue when using a Vue component inside of markdown with the Nuxt,js content module. Ended up having to do the same as @joemasilotti and using important: "html"
Also having this issue when using Vue components inside markdown with gridsome vue remark.
The important: "html"
or important: true
seem to dont work in this case.
Also did some attempts with @JasonEtco solution, but I guess this only works for css rules that are already part of the prose style, not for custom rules. Attempted the following:
typography: {
default: {
css: {
'section': false,
}
}
}
Being a <section>
the root element of the vue component.
typography: {
default: {
css: {
'div.noprose': false,
}
}
}
And wrapping the component inside a <div class="noprose">
We've done it many times on our own projects (never loved our solutions though)
@adamwathan Do u have a way to propose?
For anyone who doesn't want to lose the inline `code` style try with CSS like below which worked for me.
/* prism.css */
pre code::after {
content: none !important;
}
For those interested, there is a thread about this on Adams Twitter: https://twitter.com/adamwathan/status/1306643875994447874
TLDR; It' "impossible". That being said, there is workarounds suggested in those Tweets, but it very much depends on your setup.
The best way I found to override 'prose' styles is to setimportant: true
. (important: "html"
didn't work for me).
module.exports = {
important: true,
...
}
Now add any tailwind class to override 'prose' style.
<div class='prose'> <img class="w-32 my-0" src="/nb.svg"> </div>
And if there are too many overrides needed, it's better to split divs and add prose to required divs.
Really rooting for some official 'unprose' solution which compeletly disable prose styles for current and child divs.
@tomphilpotts You can do that already by following the example in @JasonEtco's comment
@adamwathan
Looks like it doesn't work wit Tailwind 2
https://github.com/tailwindlabs/tailwindcss-typography/issues/32#issuecomment-666683597 work but when I add prose-lg
it still adds some CSS:
@larsroettig: For Tailwind 2.0 you have to use DEFAULT
instead of default
.
We've experimented with a bunch of ways to make this work, however there is always something that doesn't work. We can re-reset all the properties we know, but once you start extending the config it gets really tricky. We've solved the issues in our codebases by not trying to be smart, but by splitting up the work:
<div class="prose">
<p>This is styled with prose</p>
</div>
<p>This is <strong>not</strong> styled with prose</p>
<div class="prose">
<p>This is styled with prose</p>
</div>
One the reasons that it is hard to re-reset is for example css properties that cascade (like font-size). Once you unprose
, you have to somehow detect the font-size from right before you enabled .prose
. You could "hack" it by using the base font-size of the html tag, but that's not correct if you have something like:
<div class="text-xs text-blue-500">
<div class="prose">
<p>This is styled with prose</p>
</div>
</div>
So I believe that the best solution to this is to split it up into multiple blocks of prose classes.
This worked for me with Tailwind 2.0, though I had to specify every breakpoint found in @tailwindcss/typography/src/styles.js
:
const disabledCss = {
"code::before": false,
"code::after": false,
"blockquote p:first-of-type::before": false,
"blockquote p:last-of-type::after": false,
pre: false,
code: false,
'pre code': false,
'code::before': false,
'code::after': false
}
module.exports = {
...
theme: {
extend: {
typography: {
DEFAULT: { css: disabledCss },
sm: { css: disabledCss },
lg: { css: disabledCss },
xl: { css: disabledCss },
'2xl': { css: disabledCss },
}
},
}
}
This is my try to implement unprose tag, work for almost all the case i need it :
https://github.com/RobinDev/tailwindcss-typography/commit/0e92e456b8f488556c0efbe717bc33f4a8d6ea33
(to see in action : git clone git@github.com:RobinDev/tailwindcss-typography.git && cd tailwindcss-typography && yarn dev
)
You can also add the following code to tailwind.config.js
.
theme: {
extend: {
typography: (theme) => ({
unprose: {
css: {
h1: {
'margin-top': '0',
'margin-bottom': '0',
},
h2: {
'margin-top': '0',
'margin-bottom': '0',
},
h3: {
'margin-top': '0',
'margin-bottom': '0',
},
h4: {
'margin-top': '0',
'margin-bottom': '0',
},
p: {
'margin-top': '0',
'margin-bottom': '0',
},
},
},
}),
},
},
Change it to your liking. I wanted to remove margin in some cases. Just add prose-unprose
.
You can also add the following code to
tailwind.config.js
.theme: { extend: { typography: (theme) => ({ unprose: { css: { h1: { 'margin-top': '0', 'margin-bottom': '0', }, h2: { 'margin-top': '0', 'margin-bottom': '0', }, h3: { 'margin-top': '0', 'margin-bottom': '0', }, h4: { 'margin-top': '0', 'margin-bottom': '0', }, p: { 'margin-top': '0', 'margin-bottom': '0', }, }, }, }), }, },
Change it to your liking. I wanted to remove margin in some cases. Just add
prose-unprose
.
Doesn't work in all cases.
what if plugin generate classes like this.
.prose ul > li:not(.prose-off):before {
content: "";
position: absolute;
background-color:red;
border-radius: 50%;
width: 0.375em;
height: 0.375em;
top: calc(0.875em - 0.1875em);
left: 0.25em;
}
One approach to solving this for MDX users would be to use a class or data attribute to indicate which elements should be styled with pose:
Before:
.prose ul > li {
position: relative;
padding-left: 1.75em;
}
After:
.prose ul[data-prose=\\"true\\"] > li[data-prose=\\"true\\"] {
position: relative;
padding-left: 1.75em;
}
Then people can configure MDX with custom ul
and li
components that add data-prose=true
. This would only be applied on markdown lists, and not to plain ul
elements inside custom components.
I have a branch for this: https://github.com/DylanVann/tailwindcss-typography/commit/96a8f3f925bccbfa0a5fbe57ddd4bf9f2ccddbb7
It would be possible to make the data attribute configurable, but that would be a breaking change.
This technique does require that you have a way to add the data attribute to the elements you want styled with prose. The way I'm doing this with MDX is:
const getComponentWithDataAttribute = (TagName: string) => {
const Comp = (props: any) => (
<TagName data-prose="true" {...props} />
)
Comp.displayName = `${TagName}Component`
return Comp
}
export const mdxComponents = {
p: getComponentWithDataAttribute('p'),
strong: getComponentWithDataAttribute('strong'),
a: getComponentWithDataAttribute('a'),
ul: getComponentWithDataAttribute('ul'),
ol: getComponentWithDataAttribute('ol'),
li: getComponentWithDataAttribute('li'),
h1: getComponentWithDataAttribute('h1'),
h2: getComponentWithDataAttribute('h2'),
// etc.
}
Since I was struggling with this myself: with the new JIT-compiler there now is the possibility to use the !important modifier.
So given the following unwanted overwrite by prose (it would overwrite the font-medium style with prose-presets)
<div class="prose">
<h1 class="font-medium">
</h1>
</div>
you can now simply change "font-medium" to "!font-medium" and overwrite prose behaviour to achieve the desired outcome.
Yep a way to define specificity is definitely needed.
So my use case is I have a set of styles that needs to be applied app wide,
//tailwind.config.js
pre: {
backgroundColor: theme('colors.gray.900'),
// borderRadius: 0,
border: 'none',
margin: 0,
code: {
backgroundColor: theme('colors.gray.900'),
borderRadius: 0,
},
},
and then a custom CSS in a single component:
/* code-styles.css */
.code {
@apply mt-0 rounded-t-none;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.16), 0 3px 3px rgba(0, 0, 0, 0.23);
border: 1px solid hsl(225, 11%, 24%);
}
Using it:
<pre className="mt-0 rounded-t-none code">{props.children}</pre>
What I expect is the css styles .code
override the prose styles.
But it happens the other way around:
How to make the prose go lower in terms of specificity?
Another option for the original problem is to try replacing the link selector with something more specific: so replacing .prose a
with .prose a:not(.button)
. If there is a way to access the defaults set by the plugin then it could be something like:
extend: {
typography: (theme) => ({
DEFAULT: {
css: {
a: null,
'a:not(.button)': theme('get.default.styles.for.a')
}
}
}
}
The idea of an unprose class will not work in my case as the client wants to have inline links and anchor tags in their articles and it would require some html on their part, which we all know will not end well.
@adamwathan and whoever else,
Now that complex selectors in :not()
are widely supported, there's actually a robust solution to this that doesn't require !important
(see my CodePen for an ugly but working example and some notes). Here it is:
Never use selectors like .prose p
at all. Instead, use selectors like .prose p:not(.no-prose *)
. Then just wrap any embedded components in a div.no-prose
, and you're good.
To allow a few more "layers" (so that embedded components can have their own .prose
which can have their own .no-prose
which can have their own .prose
), go deeper:
.prose p:not(.no-prose *),
.no-prose .prose p:not(.no-prose .no-prose *),
.no-prose .prose .prose p
(surely anything more is overkill?)
I've put together a custom PostCSS 8 plugin that transforms all the relevant Typography selectors to the above syntax. I run it after the tailwindcss
and autoprefixer
plugins, and it seems to be working. Here is the main logic:
Once (root) {
// assuming default TW breakpoints; customize as needed, or grab programmatically if feasible
const proseRegExp = /^\.(?:(?:sm|md|lg|xl|\\32xl)\\:)?prose(?:-\S+)? .+/;
const noProseClass = '.no-prose';
root.walkRules(rule => {
if (!proseRegExp.test(rule.selector)) return;
rule.selectors = rule.selectors.flatMap(selector => {
if (!proseRegExp.test(selector)) return [selector];
const [main, _pseudoEl] = selector.split('::');
const pseudoEl = _pseudoEl ? `::${_pseudoEl}` : '';
return [
`${main}:not(${noProseClass} *)${pseudoEl}`,
`${noProseClass} ${main}:not(${noProseClass} ${noProseClass} *)${pseudoEl}`,
`${noProseClass} .prose ${selector}`
];
});
});
}
To finish the job, I manually "undo" the inheritable top-level .prose
rules (color
, font-size
, and line-height
, I think) by setting them on .no-prose
to their body
values.
Thanks! Reopening so the solution is on our radar to explore 👍🏻
Originally posted by @neupauer here:
Idea 💡
Hey, I probably shouldn't put this in this PR :)
I got an idea how to make a
.no-prose
utility that disables styles inside.prose
container.<div class="prose"> <!-- Styled --> <div class="no-prose"> <!-- NOT Styled --> </div> <!-- Styled --> </div>
.prose :where(_SELECTOR_):not(:where(.no-prose _SELECTOR_))
Proof of concept here: https://play.tailwindcss.com/V4LPQ3jV46?file=css - it seems to work quite well
I'll try to find if there are any edge cases where it won't work, and also I don't know yet how it affects the specificity.
I played with this approach and so far I'm pretty impressed with what can be done with this solution:
https://play.tailwindcss.com/dwWUdjHbF5?file=css
Here's what the typography selectors look like:
.prose :where(ul > li p):not(:where(.unprose *)) {
margin-top: 2.75em;
margin-bottom: 2.75em;
color: red;
}
.prose :where(ul > li > *:first-child):not(:where(.unprose *)) {
margin-top: 1.25em;
color: blue;
}
.prose :where(ul > li > *:last-child):not(:where(.unprose *)) {
margin-bottom: 1.25em;
color: green;
}
The only remaining issue is the styles that are directly set on .prose
, and not on a child. Would need to do something like what @MichaelAllenWarner suggested and reset those to their body
values but I still think it's going to be hard to make that bullet proof (no way to know the custom values someone has set on body).
One solution could be to just not set any styles directly on .prose
and only target children? I think maybe that could work, since we don't ever write raw content directly in a prose block. There's always a <p>
, <h2>
, etc...
@adamwathan
I wonder if performance differs much between the two approaches. A point in favor of my complex-selectors-in-:not()
approach is that it has better Safari support (as far back as v. 9 I guess? tested and working in v. 12, anyway). Though if you went with :where()
then maybe a :matches() fallback would do the trick?
Regardless of implementation, this feature is a game-changer. Suddenly devs can allow CMS-users to embed just about any component they want into WYSIWYG fields without having to worry about styling conflicts.
And I'd urge you to consider accommodating a couple of "layers," as my previous post demonstrates. That way it even becomes possible to embed components that have their own WYSIWYG fields (I'm sure I'm not the only developer who's faced this situation).
The final touch is container queries to make components that are usually full-width work well in the narrower .prose
context (I've described my solution here).
The only remaining issue is the styles that are directly set on
.prose
, and not on a child. Would need to do something like what @MichaelAllenWarner suggested and reset those to theirbody
values but I still think it's going to be hard to make that bullet proof (no way to know the custom values someone has set on body).One solution could be to just not set any styles directly on
.prose
and only target children? I think maybe that could work, since we don't ever write raw content directly in a prose block. There's always a<p>
,<h2>
, etc...
Would .prose *
suffice? Given the :not()
/:where()
/whatever treatment, I mean.
Hey @MichaelAllenWarner
I wonder if performance differs much between the two approaches. A point in favor of my complex-selectors-in-
:not()
approach is that it has better Safari support (as far back as v. 9 I guess? tested and working in v. 12, anyway). Though if you went with:where()
then maybe a :matches() fallback would do the trick?
The reason for the :where()
pseudo-class is that it has lower (0) specificity. (See https://github.com/tailwindlabs/tailwindcss-typography/pull/203)
And I'd urge you to consider accommodating a couple of "layers," as my previous post demonstrates. That way it even becomes possible to embed components that have their own WYSIWYG fields (I'm sure I'm not the only developer who's faced this situation).
Multiple "layers" can be added with the following selector (I wish for a better solution):
there's a pattern in it, so it can probably be a configurable option for how many layers you need to support
.prose :where(ul > li p):not(:where(.unprose *:not(:where(.unprose .prose *:not(:where(.unprose .prose .unprose *)))))) {}
... btw I have no idea how and why it works 🤷😄, but here is a demo https://play.tailwindcss.com/xgL5FccdSy?file=css
@neupauer @adamwathan
I do see the benefit of :where()
's 0-specificity: even without a .no-prose
wrapper, you can tweak a .prose
style with an in-markup utility-class (preserving all the other .prose
styles in the process). Of course, you can already get the same result by customizing the typography
section of the Tailwind config (add a .prose p[class~="text-red"]
rule or whatever), but the :where()
method is obviously much nicer.
Lack of Safari 13 support is a big deal, though, and would probably prevent a lot of users from upgrading for a year or two. Honestly, even Safari 12 is still a consideration for many major projects; earlier this month I had to fix something for a client only because it was broken in Safari 12, and they'd received multiple complaints about it over the course of a week. Must be mostly folks on old devices who haven't upgraded to macOS 10.13 / iOS 13, but there's enough of them to matter.
I'd argue that as long as the selector is set up to "skip over" the .no-prose
sections, then the killer feature is in place, and the 0-specificity benefit isn't crucial. It's clear that :where()
is indeed the superior solution, but I just can't use it yet, and I'm sure I'm not alone. If you go that route, I'll use my :not()
approach and hold off on upgrading Typography for a while.
Yes I agree, the .unprose
(.no-prose
) feature can be definitely implemented without :where()
.
- .prose :where(ul > li p):not(.unprose *)
+ .prose ul > li p:not(.unprose *)
also with layers ...:not(.unprose *:not(.unprose .prose *))
As adamwathan mentions in #203, there might be an option to preserve the old behavior.
For example, adding important: "#app"
to tailwind.config.js
can increase the priority of utilities over .prose
selectors. So this could be "0-specificity" work-around for older browsers.
// tailwind.config.js
module.exports = {
+ important: "#app",
}
@neupauer
Yes, I have a working demo here: https://codepen.io/newcitymike/pen/bGBzrGP
@MichaelAllenWarner So it looks like the browser support for :not
with a selector list isn't actually much better than :where
— it's not supported in Samsung Internet 14 which has like 3% market share (it's supported in 15 which just came out though). :not
does work in old Safari like you mentioned though.
Do you have to support Samsung Internet in your projects? It's a popular Android browser.
Just trying to figure out if we need separate options for enabling both :where
and :not
support or if we can lump them together under some sort of target: 'modern'
type of option.
@adamwathan
I'm not obligated to support Samsung beyond the content actually displaying on the page. My hunch is that a lot of developers would be in the same boat (Safari support is crucial, Samsung not so much), but it's just a hunch.
In any case, I already have a working :not
PostCSS solution, so I'd just use that for a while without target: 'modern'
if you went with :where
.
Thank you, this will do perfectly.
thanks it works using flowbite
& flowbite-react
too
repo https://github.com/dimaslanjaka/hexo-webpack/tree/d10bb76801e81b57f6de26329f9481069dd202bf
const typographyShared = {
css: {
// codeblock are handled with highlight.js
// disable tailwind typography for codeblocks
pre: false,
code: false,
'pre code': false,
'code::before': false,
'code::after': false
}
};
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx}',
'./public/**/*.html',
// tw-elements
// './node_modules/tw-elements-react/dist/js/**/*.js',
// flowbite
'./node_modules/flowbite/**/*.js',
'./node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}'
],
theme: {
extend: {
typography: {
DEFAULT: typographyShared,
sm: typographyShared,
md: typographyShared,
lg: typographyShared,
xl: typographyShared,
'2xl': typographyShared
}
}
},
variants: {
extend: {}
},
darkMode: 'class',
// tw-elements
// plugins: [require('tw-elements-react/dist/plugin.cjs')]
// flowbite
plugins: [require('@tailwindcss/typography'), require('flowbite/plugin')]
};
adding not-prose
to the element worked for me if someone came here by a google search
Therefore a utility class should always override a plugin/theme like prose, otherwise it's not a utility class.
To do this you just need to use the important feature in Tailwind. We have no ability to control how specificity works in CSS in any other way, just how CSS works.
@adamwathan
I'm not sure I support your response: He's making an argument that utilities should always override plugins. You're essentially saying "No they shouldn't, because given how we've implemented it, css precedence makes that impossible." That doesn't seem to address his "should" argument. It begs the question of why you can't implement it so that it works as he suggesst. I can think of several ways off the top of my head: one is to put the plugin evals higher in the outputted css, or prefix them or something like that.
(Of course you can disagree with his assertion, but just saying "that's now how it works, given how it's implemented" is sort of a non-answer, no?)
@adamwathan
I must say, I find the not-prose
solution kind of half baked. It essentially allows you to style with only the prose stylings, or dump all of them wholesale. I would love to see an implementation where the prose styles get applied, and then tw classes inline on the element get applied after. That is how one would intuit it working, right?
For example, I'd expect the h1
to get all the styling that prose
would normally confer on an h1
, but also bold:
<div className='prose'>
<h1 className='font-bold'>My Special Heading</h1>
</div>
In a section of text, from Markdown (
.Content
in Hugo), I have a shortcode that is rendering a button. Prose is overriding the styling of the font styling and is underlining the text in the button. I can't seem to override this with TW classes on that element. If I disable prose on the higher-level container, then it works as expected, but then I lose all the nice styling for the rest of the content.I am suggesting a 'no-prose' class that can disable the application of prose styles to that element and all children of it.