Post image
Strategy

Can any JS wizard’s unlock the reddit ‘Post’ button programa…


Lotta confident JS aficionado’s, but still:

no one has been able to click this button programatically yet.

Post image

As described in this stackoverflow post:

https://stackoverflow.com/questions/60218444/cant-seem-to-simulate-a-keypress-in-javascript-that-unlocks-the-reddit-post?noredirect=1#comment106515059_60218444

Can test it out in the console at:

https://www.reddit.com/user/[yourUsername]/submit



Source link

A Complete Guide to Links and Buttons
Strategy

A Complete Guide to Links and Buttons


Links

Links are one of the most basic, yet deeply fundamental and foundational building blocks of the web. Click a link, and you move to another page or are moved to another place within the same page.

Table of Contents

A basic link

<a href="https://css-tricks.com">CSS-Tricks</a>

That’s a link to a “fully qualified” or “absolute” URL.

A relative link

You can link “relatively” as well:

<!-- Useful in navigation, but be careful in content that may travel elsewhere (e.g. RSS) -->
<a href="https://css-tricks.com/pages/about.html">About</a>

That can be useful, for example, in development where the domain name is likely to be different than the production site, but you still want to be able to click links. Relative URLs are most useful for things like navigation, but be careful of using them within content — like blog posts — where that content may be read off-site, like in an app or RSS feed.

A jump link

Links can also be “hash links” or “jump links” by starting with a #:

<a href="https://css-tricks.com/#section-2">Section Two</a>
<!-- will jump to... -->
<section id="section-2"></section>

Clicking that link will “jump” (scroll) to the first element in the DOM with an ID that matches, like the section element above.

💥 Little trick: Using a hash link (e.g. #0) in development can be useful so you can click the link without being sent back to the top of the page like a click on a # link does. But careful, links that don’t link anywhere should never make it to production.

💥 Little trick: Jump-links can sometimes benefit from smooth scrolling to help people understand that the page is moving from one place to another.

It’s a fairly common UI/UX thing to see a “Back to top” link on sites, particularly where important navigational controls are at the top but there is quite a bit of content to scroll (or otherwise navigate) through. To create a jump link, link to the ID of an element that is at the top of the page where it makes sense to send focus back to.

<a href="https://css-tricks.com/#top-of-page">Back to Top</a>

Jump links are sometimes also used to link to other anchor (<a>) elements that have no href attribute. Those are called “placeholder” links:

<a id="section-2"></a>
<h3>Section 2</h3>

There are accessibility considerations of these, but overall they are acceptable.

Disabled links

A link without an href attribute is the only practical way to disable a link. Why disable a link? Perhaps it’s a link that only becomes active after logging in or signing up.

a:not[href] {
  /* style a "disabled" link */
}

When a link has no href, it has no role, no focusability, and no keyboard events. This is intentional.

Do you need the link to open in a new window or tab?

You can use the target attribute for that, but it is strongly discouraged.

<a href="https://css-tricks.com" target="_blank" rel="noopener noreferrer">
  CSS-Tricks
</a>

The bit that makes it work is target="_blank", but note the extra rel attribute and values there which make it safer and faster.

Making links open in new tabs is a major UX discussion. We have a whole article about when to use it here. Summarized:

Don’t use it:

  • Because you or your client prefer it personally.
  • Because you’re trying to beef up your time on site metric.
  • Because you’re distinguishing between internal and external links or content types.
  • Because it’s your way out of dealing with infinite scroll trickiness.

Do use it:

  • Because a user is doing something on the current page, like actively playing media or has unsaved work.
  • You have some obscure technical reason where you are forced to (even then you’re still probably the rule, not the exception).

Need the link to trigger a download?

The download attribute on a link will instruct the browser to download the linked file rather than opening it within the current page/tab. It’s a nice UX touch.

<a href="https://css-tricks.com/files/file.pdf" download>Download PDF</a>

The rel attribute

This attribute is for the relationship of the link to the target.

The rel attribute is also commonly used on the <link> element (which is not used for creating hyperlinks, but for things like including CSS and preloading). We’re not including rel values for the <link> element here, just anchor links.

Here are some basic examples:

<a href="https://css-tricks.com/page/3" rel="next">Next</a>
<a href="https://css-tricks.com/page/1" rel="prev">Previous</a>

<a href="http://creativecommons.org/licenses/by/2.0/" rel="license">cc by 2.0</a>

<a href="https://css-tricks.com/topics/" rel="directory">All Topics</a>
  • rel="alternate": Alternate version of the document.
  • rel="author": Author of the document.
  • rel="help": A resource for help with the document.
  • rel="license": License and legal information.
  • rel="manifest": Web App Manifest document.
  • rel="next": Next document in the series.
  • rel="prev": Previous document in the series.
  • rel="search": A document meant to perform a search in the current document.

There are also some rel attributes specifically to inform search engines:

  • rel="sponsored": Mark links that are advertisements or paid placements (commonly called paid links) as sponsored.
  • rel="ugc": For not-particularly-trusted user-generated content, like comments and forum posts.
  • rel="nofollow": Tell the search engine to ignore this and not associate this site with where this links to.

And also some rel attributes that are most security-focused:

  • rel="noopener": Prevent a new tab from using the JavaScript window.opener feature, which could potentially access the page containing the link (your site) to perform malicious things, like stealing information or sharing infected code. Using this with target="_blank" is often a good idea.
  • rel="noreferrer": Prevent other sites or tracking services (e.g. Google Analytics) from identifying your page as the source of clicked link.

You can use multiple space-separated values if you need to (e.g. rel="noopener noreferrer")

And finally, some rel attributes come from the microformats standard, like:

  • rel="directory": Indicates that the destination of the hyperlink is a directory listing containing an entry for the current page.
  • rel="tag": Indicates that the destination of that hyperlink is an author-designated “tag” (or keyword/subject) for the current page.
  • rel="payment": Indicates that the destination of that hyperlink provides a way to show or give support for the current page.
  • rel="help": States that the resource linked to is a help file or FAQ for the current document.

ARIA roles

The default role of a link is link, so you do not need to do:

<a role="link" href="https://css-tricks.com/">Link</a>

You’d only need that if you were faking a link, which would be a weird/rare thing to ever need to do, and you’d have to use some JavaScript in addition to this to make it actually follow the link.

<span class="link" tabindex="0" role="link" data-href="https://css-tricks.com/">
  Fake accessible link created using a span
</span>

Just looking above you can see how much extra work faking a link is, and that is before you consider that is breaks right-clicking, doesn’t allow opening in a new tab, doesn’t work with Windows High Contrast Mode and other reader modes and assistive technology. Pretty bad!

A useful ARIA role to indicate the current page, like:

<a href="https://css-tricks.com/" aria-current="page">Home</a>
<a href="https://css-tricks.com/contact">Contact</a>
<a href="https://css-tricks.com/about">About/a></a>

Should you use the title attribute?

Probably not. Save this for giving an iframe a short, descriptive title.

<a title="I don't need to be here" href="https://css-tricks.com/">
  List of Concerts
</a>

title provides a hover-triggered UI popup showing the text you wrote. You can’t style it, and it’s not really that accessible.

Hover-triggered is the key phrase here. It’s unusable on any touch-only device. If a link needs more contextual information, provide that in actual content around the link, or use descriptive text the link itself (as opposed to something like “Click Here”).

Icon-only links

If a link only has an icon inside it, like:

<a href="https://css-tricks.com/">😃</a>

<a href="https://css-tricks.com/">
  <svg> ... </svg>
</a>

That isn’t enough contextual information about the link, particularly for accessibility reasons, but potentially for anybody. Links with text are almost always more clear. If you absolutely can’t use text, you can use a pattern like:

<a href="https://css-tricks.com/">
  <!-- Hide the icon from assistive technology -->
  <svg aria-hidden="true" focusable="false"> ... </svg>
  <!-- Acts as a label that is hidden from view -->
  <span class="visually-hidden">Useful link text</span>
</a>

visually-hidden is a class used to visually hide the label text with CSS:

.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

Unlike aria-label, visually hidden text can be translated and will hold up better in specialized browsing modes.

Links around images

Images can be links if you wrap them in a link. There is no need to use the alt text to say the image is a link, as assistive technology will do that already.

<a href="https://css-tricks.com/buy/puppies/now">
  <img src="https://css-tricks.com/puppy.jpg" alt="A happy puppy.">
</a>

Links around bigger chunks of content

You can link a whole area of content, like:

<a href="https://css-tricks.com/article/">
  <div class="card">
    <h2>Card</h2>
    <img src="https://css-tricks.com/..." alt="https://css-tricks.com/...">
    <p>Content</p>
  </div>
</a>

But it’s slightly weird, so consider the UX of it if you ever do it. For example, it can be harder to select the text, and the entire element needs styling to create clear focus and hover states.

Take this example, where the entire element is wrapped in a link, but there are no hover and focus states applied to it.

If you need a link within that card element, well, you can’t nest links. You could get a little tricky if you needed ot, like using a pseudo-element on the link which is absolutely positioned to cover the whole area.

Additionally, this approach can make really long and potentially confusing announcements for screen readers. Even though links around chunks of content is technically possible, it’s best to avoid doing this if you can.

Here’s the default look of a link:

The default User-Agent styling of a link.

It’s likely you’ll be changing the style of your links, and also likely you’ll use CSS to do it. I could make all my links red in CSS by doing:

a {
  color: red;
}

Sometimes selecting and styling all links on a page is a bit heavy-handed, as links in navigation might be treated entirely differently than links within text. You can always scope selectors to target links within particular areas like:

/* Navigation links */
nav a { }

/* Links in an article */
article a { }

/* Links contained in an element with a "text" class */
.text a { }

Or select the link directly to style.

.link {
  /* For styling <a class="link" href="https://css-tricks.com/"> */
}

a[aria-current="page"] {
  /* You'll need to apply this attribute yourself, but it's a great pattern to use for active navigation. */
}

Link states

Links are focusable elements. In other words, they can be selected using the Tab key on a keyboard. Links are perhaps the most common element where you’ll very consciously design the different states, including a :focus state.

  • :hover: For styling when a mouse pointer is over the link.
  • :visited: For styling when the link has been followed, as best as the browser can remember. It has limited styling ability due to security.
  • :link: For styling when a link has not been visited.
  • :active: For styling when the link is pressed (e.g. the mouse button is down or the element is being tapped on a touch screen).
  • :focus: Very important! Links should always have a focus style. If you choose to remove the default blue outline that most browsers apply, also use this selector to re-apply a visually obvious focus style.

These are chainable like any pseudo-class, so you could do something like this if it is useful for your design/UX.

/* Style focus and hover states in a single ruleset */
a:focus:hover { }

You can style a link to look button-like

Perhaps some of the confusion between links and buttons is stuff like this:

Very cool “button” style from Katherine Kato.

That certainly looks like a button! Everyone would call that a button. Even a design system would likely call that a button and perhaps have a class like .button { }. But! A thing you can click that says “Learn More” is very much a link, not a button. That’s completely fine, it’s just yet another reminder to use the semantically and functionally correct element.

Color contrast

Since we often style links with a distinct color, it’s important to use a color with sufficient color contrast for accessibility. There is a wide variety of visual impairments (see the tool WhoCanUse for simulating color combinations with different impairments) and high contrast helps nearly all of them.

Perhaps you set a blue color for links:

The blue link is #2196F3.

While that might look OK to you, it’s better to use tools for testing to ensure the color has a strong enough ratio according to researched guidelines. Here, I’ll look at Chrome DevTools and it will tell me this color is not compliant in that it doesn’t have enough contrast with the background color behind it.

Chrome DevTools is telling us this link color does not have enough contrast.

Color contrast is a big consideration with links, not just because they are often colored in a unique color that needs to be checked, but because they have all those different states (hover, focus, active, visited) which also might have different colors. Compound that with the fact that text can be selected and you’ve got a lot of places to consider contrast. Here’s an article about all that.

Styling “types” of links

We can get clever in CSS with attribute selectors and figure out what kind of resource a link is pointing to, assuming the href value has useful stuff in it.

/* Style all links that include .pdf at the end */
a[href$=".pdf"]::after {
  content: " (PDF)";
}

/* Style links that point to Google */
a[href*="https://css-tricks.com/google.com"] {
  color: purple;
}

Styling links for print

CSS has an “at-rule” for declaring styles that only take effect on printed media (e.g. printing out a web page). You can include them in any CSS like this:

@media print {
  /* For links in content, visually display the link */ 
  article a::after { 
    content: " (" attr(href) ")";
  }
}

Resetting styles

If you needed to take all the styling off a link (or really any other element for that matter), CSS provides a way to remove all the styles using the all property.

.special-area a {
  all: unset;
  all: revert;
  
  /* Start from scratch */
  color: purple;
}

You can also remove individual styles with keywords. (Again, this isn’t really unique to links, but is generically useful):

a {
  /* Grab color from nearest parent that sets it */
  color: inherit;

  /* Wipe out style (turn black) */
  color: initial;

  /* Change back to User Agent style (blue) */
  color: revert;
}

Say you wanted to stop the clicking of a link from doing what it normally does: go to that link or jump around the page. In JavaScript, you can usepreventDefault to prevent jumping around.

const jumpLinks = document.querySelectorAll("a[href^='#']");

jumpLinks.forEach(link => {
 link.addEventListener('click', event => {
    event.preventDefault();
    // Do something else instead, like handle the navigation behavior yourself
  });
});

This kind of thing is at the core of how “Single Page Apps” (SPAs) work. They intercept the clicks so browsers don’t take over and handle the navigation.

SPAs see where you are trying to go (within your own site), load the data they need, replace what they need to on the page, and update the URL. It’s an awful lot of work to replicate what the browser does for free, but you get the ability to do things like animate between pages.

Another JavaScript concern with links is that, when a link to another page is clicked, the page is left and another page loads. That can be problematic for something like a page that contains a form the user is filling out but hasn’t completed. If they click the link and leave the page, they lose their work! Your only opportunity to prevent the user from leaving is by using the beforeunload event.

window.addEventListener("beforeunload", function(event) {
  // Remind user to save their work or whatever.
});

A link that has had its default behavior removed won’t announce the new destination. This means a person using assistive technology may not know where they wound up. You’ll have to do things like update the page’s title and move focus back up to the top of the document.

JavaScript frameworks

In a JavaScript framework, like React, you might sometimes see links created from something like a <Link /> component rather than a native <a> element. The custom component probably creates a native <a> element, but with extra functionality, like enabling the JavaScript router to work, and adding attributes like aria-current="page" as needed, which is a good thing!

Ultimately, a link is a link. A JavaScript framework might offer or encourage some level of abstraction, but you’re always free to use regular links.

We covered some accessibility in the sections above (it’s all related!), but here are some more things to think about.

  • You don’t need text like “Link” or “Go to” in the link text itself. Make the text meaningful (“documentation” instead of “click here”).
  • Links already have an ARIA role by default (role="link") so there’s no need to explicitly set it.
  • Try not to use the URL itself as the text (<a href="https://css-tricks.com/google.com">google.com</a>)
  • Links are generally blue and generally underlined and that’s generally good.
  • All images in content should have alt text anyway, but doubly so when the image is wrapped in a link with otherwise no text.

Unique accessible names

Some assistive technology can create lists of interactive elements on the page. Imagine a group of four article cards that all have a “Read More”, the list of interactive elements will be like:

  • Read More
  • Read More
  • Read More
  • Read More

Not very useful. You could make use of that .visually-hidden class we covered to make the links more like:

<a href="https://css-tricks.com/article">
  Read More
  <span class="visually-hidden">
    of the article "Dancing with Rabbits".
  <span>
</a>

Now each link is unique and clear. If the design can support it, do it without the visually hidden class to remove the ambiguity for everyone.

Buttons

Buttons are for triggering actions. When do you use the <button> element? A good rule is to use a button when there is “no meaningful href.” Here’s another way to think of that: if clicking it doesn’t do anything without JavaScript, it should be a <button>.

A <button> that is within a <form>, by default, will submit that form. But aside from that, button elements don’t have any default behavior, and you’ll be wiring up that interactivity with JavaScript.

Table of Contents

HTML implementation

<button>Buy Now</button>

Buttons inside of a <form> do something by default: they submit the form! They can also reset it, like their input counterparts. The type attributes matter:

<form action="https://css-tricks.com/" method="POST">
  <input type="text" name="name" id="name">
  <button>Submit</button>

  <!-- If you want to be more explicit... -->
  <button type="submit">Submit</button>

  <!-- ...or clear the form inputs back to their initial values -->
  <button type="reset">Reset</button>

  <!-- This prevents a `submit` action from firing which may be useful sometimes inside a form -->
  <button type="button">Non-submitting button</button>
</form>

Speaking of forms, buttons have some neat tricks up their sleeve where they can override attributes of the <form> itself.

<form action="https://css-tricks.com/" method="get">

  <!-- override the action -->
  <button formaction="/elsewhere/" type="submit">Submit to elsewhere</button>

  <!-- override encytype -->
  <button formenctype="multipart/form-data" type="submit"></button>

  <!-- override method -->
  <button formmethod="post" type="submit"></button>

  <!-- do not validate fields -->
  <button formnovalidate type="submit"></button>

  <!-- override target e.g. open in new tab -->
  <button formtarget="_blank" type="submit"></button>

</form>

Autofocus

Since buttons are focusable elements, we can automatically focus on them when the page loads using the autofocus attribute:

<div class="modal">

  <h2>Save document?</h2>

  <button>Cancel</button>
  <button autofocus>OK</button>
</div>

Perhaps you’d do that inside of a modal dialog where one of the actions is a default action and it helps the UX (e.g. you can press Enter to dismiss the modal). Autofocusing after a user action is perhaps the only good practice here, moving a user’s focus without their permission, as the autofocus attribute is capable of, can be a problem for screen reader and screen magnifier users.

Note thatautofocus may not work if the element is within an <iframe sandbox> for security reasons.

Disabling buttons

To prevent a button from being interactive, there is a disabled attribute you can use:

<button disabled>Pay Now</button>
<p class="error-message">Correct the form above to submit payment.</p>

Note that we’ve included descriptive text alongside the disabled button. It can be very frustrating to find a disabled button and not know why it’s disabled. A better way to do this could be to let someone submit the form, and then explain why it didn’t work in the validation feedback messaging.

Regardless, you could style a disabled button this way:

/* Might be good styles for ANY disabled element! */
button[disabled] {
  opacity: 0.5;
  pointer-events: none;
} 

We’ll cover other states and styling later in this guide.

Buttons can contain child elements

A submit button and a submit input (<input type="submit">) are identical in functionality, but different in the sense that an input is unable to contain child elements while a button can.

<button>
   <svg aria-hidden="true" focusable="false">
     <path d="https://css-tricks.com/..." />
   </svg>
   <span class="callout">Big</span>
   Sale!
</button>

<button type="button">
  <span role="img" aria-label="Fox">
    🦊
  </span>
  Button
</button>

Note the focusable="false" attribute on the SVG element above. In that case, since the icon is decorative, this will help assistive technology only announce the button’s label.

Styling and CSS considerations

Buttons are generally styled to look very button-like. They should look pressable. If you’re looking for inspiration on fancy button styles, you’d do well looking at the CodePen Topic on Buttons.

1, 2, 3, 4, 5, 6

Cross-browser/platform button styles

How buttons look by default varies by browser and platform.

Just on macOS: Chrome, Safari, and Firefox (they look the same)
Add border: 0; to those same buttons as above, and we have different styles entirely.

While there is some UX truth to leaving the defaults of form elements alone so that they match that browser/platform’s style and you get some affordance for free, designers typically don’t like default styles, particularly ones that differ across browsers.

Resetting the default button style

Removing all the styles from a button is easier than you think. You’d think, as a form control, appearance: none; would help, but don’t count on that. Actually all: revert; is a better bet to wipe the slate clean.

You can see how a variety of properties are involved

And that’s not all of them. Here’s a consolidated chunk of what Normalize does to buttons.

button {
  font-family: inherit; /* For all browsers */
  font-size: 100%; /* For all browsers */
  line-height: 1.15; /* For all browsers */
  margin: 0; /* Firefox and Safari have margin */
  overflow: visible; /* Edge hides overflow */
  text-transform: none; /* Firefox inherits text-transform */
  -webkit-appearance: button; /* Safari otherwise prevents some styles */
}

button::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

button:-moz-focusring {
  outline: 1px dotted ButtonText;
}

A consistent .button class

In addition to using reset or baseline CSS, you may want to have a class for buttons that gives you a strong foundation for styling and works across both links and buttons.

.button {
  border: 0;
  border-radius: 0.25rem;
  background: #1E88E5;
  color: white;
  font-family: -system-ui, sans-serif;
  font-size: 1rem;
  line-height: 1.2;
  white-space: nowrap;
  text-decoration: none;
  padding: 0.25rem 0.5rem;
  margin: 0.25rem;
  cursor: pointer;
}

Check out this Pen to see why all these properties are needed to make sure it works correctly across elements.

Button states

Just as with links, you’ll want to style the states of buttons.

button:hover { }
button:focus { }
button:active { }
button:visited { } /* Maybe less so */

You may also want to use ARIA attributes for styling, which is a neat way to encourage using them correctly:

button[aria-pressed="true"] { }
button[aria-pressed="false"] { }

Link-styled buttons

There are always exceptions. For example, a website in which you need a button-triggered action within a sentence:

<p>You may open your <button>user settings</button> to change this.</p>

We’ve used a button instead of an anchor tag in the above code, as this hypothetical website opens user settings in a modal dialog rather than linking to another page. In this situation, you may want to style the button as if it looks like a link.

This is probably rare enough that you would probably make a class (e.g. .link-looking-button) that incorporates the reset styles from above and otherwise matches what you do for anchor links.

Breakout buttons

Remember earlier when we talked about the possibility of wrapping entire elements in links? If you have a button within another element, but you want that entire outer element to be clickable/tappable as if it’s the button, that’s a “breakout” button. You can use an absolutely-positioned pseudo-element on the button to expand the clickable area to the whole region. Fancy!

JavaScript considerations

Even without JavaScript, button elements can be triggered by the Space and Enter keys on a keyboard. That’s part of what makes them such appealing and useful elements: they are discoverable, focusable, and interactive with assistive technology in a predictable way.

Perhaps any <button> in that situation should be inserted into the DOM by JavaScript. A tall order! Food for thought. 🤔

“Once” handlers

Say a button does something pretty darn important, like submitting a payment. It would be pretty scary if it was programmed such that clicking the button multiple times submitted multiple payment requests. It is situations like this where you would attach a click handler to a button that only runs once. To make that clear to the user, we’ll disable the button on click as well.

document.querySelector("button").addEventListener('click', function(event) {
  event.currentTarget.setAttribute("disabled", true);
}, {
    once: true
});

Then you would intentionally un-disable the button and reattach the handler when necessary.

Inline handlers

JavaScript can be executed by activating a button through code on the button itself:

<button onclick="console.log('clicked');">
  Log it.
</button>

<button onmousedown="">
</button>

<button onmouseup="">
</button>

That practice went from being standard practice to being a faux pas (not abstracting JavaScript functionality away from HTML) to, eh, you need it when you need it. One advantage is that if you’re injecting this HTML into the DOM, you don’t need to bind/re-bind JavaScript event handlers to it because it already has one.

JavaScript frameworks

It’s common in any JavaScript framework to make a component for handling buttons, as buttons typically have lots of variations. Those variations can be turned into an API of sorts. For example, in React:

const Button = ({ className, children }) => {
  const [activated, setActivated] = React.useState(false);
  return (
    <button
      className={`button ${className}`}
      aria-pressed={activated ? "true" : "false")
      onClick={() => setActivated(!activated)}
    >
      {children}
    </button>
  );
};

In that example, the <Button /> component ensures the button will have a button class and handles a toggle-like active class.

Accessibility considerations

The biggest accessibility consideration with buttons is actually using buttons. Don’t try to replicate a button with a <div> or a <span>, which is, unfortunately, more common than you might think. It’s very likely that will cause problems. (Did you deal with focusability? Did you deal with keyboard events? Great. There’s still probably more stuff you’re forgetting.)

Focus styles

Like all focusable elements, browsers apply a default focus style, which is usually a blue outline.

Focus styles on Chrome/macOS

While it’s arguable that you should leave that alone as it’s a very clear and obvious style for people that benefit from focus styles, it’s also not out of the question to change it.

What you should not do is button:focus { outline: 0; } to remove it. If you ever remove a focus style like that, put it back at the same time.

button:focus {
  outline: 0; /* Removes the default blue ring */

  /* Now, let's create our own focus style */
  border-radius: 3px;
  box-shadow: 0 0 0 2px red;
}
Custom focus style

The fact that a button may become focused when clicked and apply that style at the same time is offputting to some. There is a trick (that has limited, but increasing, browser support) on removing focus styles from clicks and not keyboard events:

:focus:not(:focus-visible) { 
  outline: 0; 
}

ARIA

Buttons already have the role they need (role="button"). But there are some other ARIA attributes that are related to buttons:

  • aria-pressed: Turns a button into a toggle, between aria-pressed="true" and aria-pressed="false". More on button toggles, which can also be done with role="switch" and aria-checked="true".
  • aria-expanded: If the button controls the open/closed state of another element (like a dropdown menu), you apply this attribute to indicate that like aria-expanded="true".
  • aria-label: Overrides the text within the button. This is useful for labeling buttons that otherwise don’t have text, but you’re still probably better off using a visually-hidden class so it can be translated.
  • aria-labelledby: Points to an element that will act as the label for the button.

For that last one:

<button aria-labelledby="buttonText">
  Time is running out! 
  <span id="buttonText">Add to Cart</span>
</button>

Deque has a deeper dive blog post into button accessibility that includes much about ARIA.

Dialogs

If a button opens a dialog, your job is to move the focus inside and trap it there. When closing the dialog, you need to return focus back to that button so the user is back exactly where they started. This makes the experience of using a modal the same for someone who relies on assistive technology as for someone who doesn’t.

Focus management isn’t just for dialogs, either. If clicking a button runs a calculation and changes a value on the page, there is no context change there, meaning focus should remain on the button. If the button does something like “move to next page,” the focus should be moved to the start of that next page.

Size

Don’t make buttons too small. That goes for links and any sort of interactive control. People with any sort of reduced dexterity will benefit.

The classic Apple guideline for the minimum size for a touch target (button) is 44x44pt.

Here’s some guidelines from other companies. Fitt’s Law tells us smaller targets have greater error rates. Google even takes button sizes into consideration when evaluating the SEO of a site.

In addition to ample size, don’t place buttons too close each other, whether they’re stacked vertically or together on the same line. Give them some margin because people experiencing motor control issues run the risk of clicking the wrong one.

Activating buttons

Buttons work by being clicked/touched, pressing the Enter key, or pressing the Space key (when focused). Even if you add role="button" to a link or div, you won’t get the spacebar functionality, so at the risk of beating a dead horse, use <button> in those cases.



Source link

Creating image-gallery project
Strategy

Angular Image Gallery With Bootstrap


Recently I was presented with a challenge to quickly build an image gallery. The key requirements for the exercise were to use Angular 8 and Bootstrap 4. In this article, I have explained how we can achieve this with minimum time, effort, and resources. Since this is a practical use case in many applications, I hope it will help developers trying to build a similar feature. 

Note that for this tutorial, I have used Visual Studio Code on a Windows machine. However, you can follow along with any environment and IDE that support Angular application development.

Selecting the Image Source

For this tutorial, let’s use a public image feed from Flickr.

This API provides images with associated information about the uploader, date of upload, and more that we can use to build our application.  

You may also enjoy: Bootstrap 4 and Angular: A Beginner’s Guide to Customizing a Theme

Creating the Angular Project

Open your command prompt and navigate to the directory where you want to create the project and issue the  ng new command.

Here I have created a new Angular project with the name image-gallery. You can name your project according to your liking. Select “No” for adding Angular routing and CSS as the stylesheet format and hit enter to create the project. 

Creating image-gallery project

Creating image-gallery project

Clean Up And Initial Set-Up

Once the project is created, open it in your favorite IDE. On the integrated terminal window, issue the ng serve command to verify that the project builds fine and runs without any issues. By default, Angular runs the application at http://localhost:4200/.

At this point, if you open the application using the localhost URL you will see the contents of app.component.html on the page. This content is a boilerplate template injected by Angular. Go ahead and delete everything from this file. 

We will display the application name using the title property of app.component.ts. Change the title to display a suitable name for your application. 

Now in the app.component.html display this title inside H1 using string interpolation. 

At this point you should see this in your browser.

Title display

Title display

We will improve this by adding necessary services and components.

Creating the Flickr service 

In order to call the Flickr API and get the images, we would need a service. We can instruct the Angular CLI to create a service for us by issuing the  ng generate service flickr command in the terminal.  

Now let us spend some time analyzing the raw response from the Flickr API. We can see the raw response simply by issuing a get request in the browser. 

Issuing a get request

Issuing a get request

Notice that the response is wrapped inside jsonFlickrFeed. However, we need a plain JSON to work with. We can get a JSON response using an additional query string parameter nojsoncallback=1.

JSON response

JSON response

We also need to think about a way to de-serialize the complex JSON response to a suitable typescript type. This will make it easier to work with the response. We will also get typescript IntelliSense while writing binding expressions to display the images and associated information. 

Let us add a models folder under the app folder to hold the interfaces that we would use for deserializing the JSON response. For the complex subtypes, we will add separate interfaces.

Notice how the FlickerResponse interface is defined. Apart from having simple properties, it also includes property items of type Item array. The item itself is an interface with its own properties. You can follow this approach for de-serializing complex API responses. 

Let us now move on to implement the service. This is actually simple. We will make use of Angular’s HttpClientModule to issue a get request to the API. 

Notice how we have returned an Observable of type  FlickerResponse from the service method  getPhotos 

We have done a lot of hard work to come to this stage. Now it’s time to see some results. 

Building the Image Gallery

To display the images, let us create a new component. Head over to the terminal and issue the generate component command  ng generate component photo-gallery .

We need to add this component to the app component as we would be rendering all the images there. 

app-photo-gallery component

app-photo-gallery component

It’s now time to make use of the FlickrService in our photo-gallery component.

Let us understand what we have done. We have used dependency injection to inject an instance of FlickrService to the photo-gallery component. We have exposed a private property  flickrService  using which  getPhotos method of the service has been called. Finally, we are storing the response on a property flickerRespone. 

Now that we have the actual response from the service, let’s display something on the photo-gallery. We will make use of the  flickerResponse property to iterate over the items array and display the images. 

With this, we should see the photos. Instead, we see a blank page with only the title. Let’s see what information we can get from the console log. 

Console log error

Console log error

It appears that the response is being blocked by the browser as it doesn’t see a header element Access-Control-Allow-Origin in the response. This is the default behavior of most of the browsers. For security reasons, browsers block resource sharing between two different domains. Here, since our application is running on localhost and we are requesting data from api.flickr.com, chrome has blocked the response. This is a typical example of a CORS error. 

So how can we resolve it? 

If we have control over the API resource we can simply add the header Access-Control-Allow-Origin with target domains that we want to white-list or * to white-list requests from any origin.

In this case, however, since we don’t have control over the API code, we need to enable CORS manually in the browser. Now, this is a security risk and should be disabled as soon as we are done with the testing. 

Doing this manually can be tricky. Luckily, there is a Chrome extension that allows us to enable or disable CORS with a single click. You can download the extension from the chrome web store. 

Now that we have dealt with the CORS issue, can we see something on the screen? 

Well, we see images, but we are not quite there yet. In the next section let’s add BootStrap and make the gallery look like a gallery. 

Styling the Gallery

In the terminal window use command  npm install bootstrap --save  to install Bootstrap to our application. 

After installation, open styles.css file from the solution explorer and import Bootstrap as our global styles provider. 

With this, we are pretty much set to bring in some CSS magic and beautify our application. 

Let us change app.component.html as below. 

Similarly, add the below to photo-gallery.component.html.

With that let us see if our gallery looks presentable. 

Flickr gallery

Flickr gallery

As you can see now we have a beautiful mosaic layout with image cards. This type of presentation is called a Masonry column layout and is suitable when we have variable image dimensions. 

I hope this article would help you implement similar functionality in your applications. You can download source code from Github

In the upcoming article we will take it to the next level by adding an infinite scroll feature by loading new images as the user scrolls down the page. 

Further Reading

Getting Started With Angular 7.0

Angular: Everything You Need to Know [Tutorials]



Source link

Post image
Strategy

How would you go about implementing this design? : webdev


Thinking in design system, how would you approach the design below? Specifically, how would you solve the collapsing padding/gap between the first two columns, and the background that stretches out of the container.

It’s been puzzling me for days and I’m starting to run out of ideas. I’m not asking for final code, just how to conceptualize the components/classes that would work out. Like, are the first and second rows the same component? How many layers of components would you need? Which component/layer applies the gap/padding?

Thanks in advance!

Post image

Large breakpoint

Small breakpoint



Source link

Screenshot. Shows 8 vertical rainbow stripes, from left to right: violet, magenta, red, orange, yellow, yellowish green, teal, blue.
Strategy

While You Weren’t Looking, CSS Gradients Got Better


One thing that caught my eye on the list of features for Lea Verou’s conic-gradient() polyfill was the last item:

Supports double position syntax (two positions for the same color stop, as a shortcut for two consecutive color stops with the same color)

Surprisingly, I recently discovered most people aren’t even aware that double position for gradient stops is something that actually exists in the spec, so I decided to write about it.

According to the spec:

Specifying two locations makes it easier to create solid-color “stripes” in a gradient, without having to repeat the color twice.

I completely agree, this was the first thing I thought of when I became aware of this feature.

Let’s say we want to get the following result: a gradient with a bunch of equal width vertical stripes (which I picked up from an earlier post by Chris):

Screenshot. Shows 8 vertical rainbow stripes, from left to right: violet, magenta, red, orange, yellow, yellowish green, teal, blue.
Desired gradient result.

The hex values are: #5461c8, #c724b1, #e4002b, #ff6900, #f6be00, #97d700, #00ab84 and #00a3e0.

Let’s first see how we’d CSS this without using double stop positions!

We have eight stripes, which makes each of them one-eighth of the gradient width. One eighth of 100% is 12.5%, so we go from one to the next at multiples of this value.

This means our linear-gradient() looks as follows:

linear-gradient(90deg, 
             #5461c8 12.5% /* 1*12.5% */, 
  #c724b1 0, #c724b1 25%   /* 2*12.5% */, 
  #e4002b 0, #e4002b 37.5% /* 3*12.5% */, 
  #ff6900 0, #ff6900 50%   /* 4*12.5% */, 
  #f6be00 0, #f6be00 62.5% /* 5*12.5% */, 
  #97d700 0, #97d700 75%   /* 6*12.5% */, 
  #00ab84 0, #00ab84 87.5% /* 7*12.5% */, 
  #00a3e0 0)

Note that we don’t need to repeat stop position % values because, whenever a stop position is smaller than a previous one, we automatically have a sharp transition. That’s why it’s always safe to use 0 (which is always going to be smaller than any positive value) and have #c724b1 25%, #e4002b 0 instead of #c724b1 25%, #e4002b 25%, for example. This is something that can make our life easier in the future if, for example, we decide we want to add two more stripes and make the stop positions multiples of 10%.

Not too bad, especially compared to what gradient generators normally spit out. But if we decide one of those stripes in the middle doesn’t quite fit in with the others, then changing it to something else means updating in two places.

Again, not too bad and nothing we can’t get around with a little bit of help from a preprocessor:

$c: #5461c8 #c724b1 #e4002b #ff6900 #f6be00 #97d700 #00ab84 #00a3e0;

@function get-stops($c-list) {
  $s-list: ();
  $n: length($c-list);
  $u: 100%/$n;
	
  @for $i from 1 to $n {
    $s-list: $s-list, 
             nth($c-list, $i) $i*$u, 
             nth($c-list, $i + 1) 0
  }

  @return $s-list
}

.strip {
  background: linear-gradient(90deg, get-stops($c)))
}

This generates the exact CSS gradient we saw a bit earlier and now we don’t have to modify anything in two places anymore.

See the Pen by thebabydino (@thebabydino) on CodePen.

However, even if a preprocessor can save us from typing the same thing twice, it doesn’t eliminate repetition from the generated code.

And we may not always want to use a preprocessor. Leaving aside the fact that some people are stubborn or have an irrational fear or hate towards preprocessors, it sometimes feels a bit silly to use a loop.

For example, when we barely have anything to loop over! Let’s say we want to get a much simpler background pattern, such as a diagonal hashes one, which I’d imagine is a much more common use case than an over-the-top rainbow one that’s probably not a good fit on most websites anyway.

Screenshot. Shows a pattern of diagonal light grey hashes on a white background.
Desired hashes result

This requires using repeating-linear-gradient() and this means a bit of repetition, even if we don’t have the same long list of hex values as we did before:

repeating-linear-gradient(-45deg, 
    #ccc /* can't skip this, repeating gradient won't work */, 
    #ccc 2px, 
    transparent 0, 
    transparent 9px /* can't skip this either, tells where gradient repetition starts */)

Here, we cannot ditch the first and last stops because those are precisely what indicate how the gradient repeats within the rectangle defined by the background-size.

If you want to understand why it’s better to use repeating-linear-gradient() instead of a plain old linear-gradient() combined with the proper background-size in order to create such hashes, check out this other article I wrote a while ago.

This is precisely where such feature comes to the rescue — it allows us to avoid repetition in the final CSS code.

For the rainbow stripes case, our CSS becomes:

linear-gradient(90deg, 
    #5461c8 12.5%, 
    #c724b1 0 25%, 
    #e4002b 0 37.5%, 
    #ff6900 0 50%, 
    #f6be00 0 62.5%, 
    #97d700 0 75%, 
    #00ab84 0 87.5%, 
    #00a3e0 0)

And to recreate the hashes, we only need:

repeating-linear-gradient(-45deg, 
    #ccc 0 2px, 
    transparent 0 9px)

See the Pen by thebabydino (@thebabydino) on CodePen.

What about support? Well, glad you asked! It actually happens to be pretty good! It works in Safari, Chromium browsers (which now includes Edge as well!) and Firefox. Pre-Chromium Edge and maybe some mobile browsers could still hold you back, but if you don’t have to worry about providing support for every browser under the sun or it’s fine to provide a fallback, go ahead and start using this!



Source link

Tutorial: How to Build a Progressive Web App (PWA)
Strategy

Tutorial: How to Build a Progressive Web App (PWA)


You need a native app. That’s what we’ve been told repeatedly since Apple first announced the iPhone App Store. And perhaps you do. Native apps can make sense, depending on an organization’s size and needs.

But what about potential customers who don’t have your app? Or current customers on a desktop computer? What about people with limited space on their phones who delete apps to make room for other things? What is their experience like?

This is where Progressive Web Apps (sometimes referred to as PWAs) shine. They combine the best features of the web with capabilities previously only available to native apps. Progressive Web Apps can be launched from an icon on the home screen or in response to a push notification. They load nearly instantaneously and can be built to work offline.

Best of all, Progressive Web Apps simply work. They are an enhancement to your website. No one needs to install anything to use a Progressive Web App. The first time someone visits your website, the features of a PWA are available immediately — no app stores (unless you want them). No gatekeepers. No barriers.                                                                        

You may also like:
Developing a PWA Using Angular 7.

Requirements

To start with this tutorial you must install the following:

  • Stable node version 8.9 or higher (https: // nodejs.org/en/download/).

  • Yarn (https://yarnpkg.com)

  • Git

As a starting point for the tutorial, clone this Github repository: 

then, move to the following directory: 

and install the dependencies through:

                                                                        


Open your app on: 
http:// localhost:8080  
Initial landing page
Initial landing page

What Are the Technical components of a PWA?

A PWA has three important technical components that work together, including:

Manifest file, the Service Worker, and the PWA must run under https.

Three components of PWA

Three components of PWA

Manifest File

The Manifest file is a configuration JSON file that contains the information from your PWA, such as the icon that appears on the home screen when it is installed, the short name of the web app, or the background color. If the Manifest file is present, Chrome automatically activates the banner for installing the web app (the “Add to Home Screen” button). If the user agrees, the icon will be added to the home screen, and the PWA will be installed.

Install PWA

Install PWA

Create a Manifest.json   

A Manifest.json file for a PWA looks like this:

Tell the Browser About Your Manifest

Create a Manifest.json file at the same level as your index.html file.

When you have created the Manifest, add a link-tag to your index.html (between the other links tags).

Manifest Properties

You must at least specify the property, short_name or name. Short_name is used on the user’s home screen. Name is used in the app’s install prompt. When a user adds your PWA to their home screen, you can define a set of icons that the browser should use.

These icons are used in places, such as the home screen and the app launcher. The start_url property tells the browser where it should start the application. The scope defines the set of URLs that the browser considers to be within your app and is used to decide when the user has left the app and should be bounced back out to a browser tab. Your start_url property must be within the scope. The display property indicates the following display modes:

  • Fullscreen: all available space is used for the app.
  • Stand-alone: the application has the look and feel of a stand-alone application.

The background_color property is used on the splash screen when the application is started.

From this point, the “Add to Home Screen” button does not work yet. But, you can already experiment with the Manifest file. There are currently various tools with which you can generate your Manifest file with, such as https://app-Manifest.firebaseapp.com/..

Add the Manifest to Your Application

Generate your own Manifest and place it at the same level as the index.html file. Check your Chrome Developer Tools and see if your Manifest is active. Right-click on the homepage of the Progressive Selfies App and select “Inspect“. Select the “Application” tab and choose “Manifest“. Restart the server with npm start if necessary.

Starting manifest file

Starting Manifest file

What Is a Service Worker?

A Service Worker (SW) is just a piece of JavaScript that works as a proxy between the browser and the network. A SW supports push notifications, background sync, caching, etc. The core feature discussed in this tutorial allows PWAs to intercept and handle network requests, including programmatically managing a cache of responses.

The reason this is such an exciting API is that it allows you to support offline experiences by taking advantage of cache, giving developers complete control over a user’s experience.

Service Worker workflow

Service Worker workflow

The Service Worker Lifecycle

With Service Workers, the following steps are taken for the basic setting:

  • Register your SW. You must first register it. If the SW is registered, the browser automatically starts the installation based on an install event.
  • When the SW is installed, it receives an activate event. This activation event can be used to clean up resources used in earlier versions of an SW.

Service Worker lifecycle

Service Worker lifecycle

                               

For the SW, create an empty file named sw.js at the same level as your index.html file. And in the index.html file, add a base tag (between the other links tags in the <head> section). This is the base URL for all your relative links in your app:

    

Finally, add the code below in src/js/app.js to register the SW. This code is activated during the “loading” of the first page.

This code checks whether the API of the SW is available in the navigator property of the window object. The window object represents the browser window. (Javascript and the navigator property is part of the window object.). If the SW is available in the navigator, the SW is registered as soon as the page is loaded.

You can now check whether an SW is enabled in the Chrome Developer Tools in the Application -> Service Workers tab. Refresh the page for this!

    

Why Can’t I Register my Service Worker?

This could happen for a couple of reasons:

  • Your app could not be running under HTTPS. During development, you can use the SW via localhost. But, if you deploy it on a site, then you need an HTTPS setup.
  • The path of the SW is not correct.
  • Update on reload must be checked during development!

Checking update on reload

Checking update on reload

Service Worker Events

In addition to install and activate, we have a messagingevent. This event takes place when a message is received from another script in the web app. The fetchevent is triggered when a page from your site requires a network resource. It can be a new page, a JSON API, an image, a CSS file, etc.

The
sync event is sent if the browser previously detected that the connection was unavailable and signals the Service Worker that the internet connection is working. The
push
event is invoked by the Push API when a new push event is received from the backend.                                                      

Service Worker events

Service Worker events

Add the following code to your SW to listen to the lifecycle events (install and activate):

The install callback is calling the skipWaiting() function to trigger the activate event and tell the Service Worker to start working immediately without waiting for the user to navigate or reload the page.

The skipWaiting() function forces the waiting Service Worker to become the active Service Worker. The self.skipWaiting() function can also be used with the self.clients.claim() function to ensure that updates to the underlying Service Worker take effect immediately.

In this context, the self-property represents the window object (ie your browser window).                     

Add to Home Screen Button

The “Add to Home Screen button” allows a user to install the PWA on their device. In order to actually install the PWA with this button, you must define a fetch event handler in the SW. Let’s fix that in the sw.js.

Check Update on reloadUnregister your SW in Chrome Dev tools, and refresh your screen. Go to your PWA (on localhost:8080), click the “Customize button” in Chrome, and select: Install Progressive Selfies ….

Installing Progressive Selfies

Installing Progressie Selfies

After this, the “install banner” is shown.

Install banner

Install banner

Service Worker Caching

The power of Service Workers lies in their ability to intercept HTTP requests. In this step, we use this option to intercept HTTP requests and responses to provide users with a lightning-fast response directly from the cache.

Precaching During Service Worker Installation                                   

When a user visits your website for the first time, the SW starts to install itself. During this installation phase, you can fill the cache with all pages, scripts, and styling files that the PWA uses. Complete the sw.js file as follows:

This code uses the install event and adds an array of URLS_TO_PRECACHE files at this stage. You can see that once the cache is open (caches.open), you can then add files using the cache.addAll(). The event.waitUntil() method uses a JavaScript promise to know how long installation takes and whether it succeeded.

The install event calls the self.skipWaiting() to activate the SW directly. If all files have been successfully cached, the SW will be installed. If one of the files cannot be downloaded, the installation step fails. In the Chrome Developer Tools, you can check whether the cache (in the Cache Storage) is filled with the static files from the URLS_TO_PRECACHE array.

Checking cache

Checking cache

But, if you look in the Network tab (even after a refresh) the files are still fetched over the network. The reason is that the cache is primed and ready to go, but we are not reading assets from it. In order to do that we need to add the code in the next listing to our Service Worker in order to start listening to the existing fetch event.

We are checking if the incoming URL matches anything that might exist in our current cache using the caches.match() function. If it does, we return that cached resource, but if the resource doesn’t exist in the cache, we continue as normal and fetch the requested resource.

After the Service Worker installs and activates, refresh the page and check the Network tab again. The Service Worker will now intercept the HTTP request and load the appropriate resources instantly from the cache instead of making a network request to the server.

Now, if we set Offline mode in the Network tab our cached app will look like this:
Application after caching

Application after caching

Service Workers

Service Workers

                                                                        

Background Fetch

The Background Fetch API is a SW background feature that makes it possible to download large files, movies, podcasts, etc. in the background. During the fetch/transfer, your user can choose to close the tab or even close the entire browser.

This will not stop the transfer. After the browser is opened again, the transfer will resume. This API can also handle poor accessibility. The progress of the transfer can be shown to the user, and the user can cancel or pause this process.

Pausing background fetch

Pausing Background Fetch

Finally, your PWA has access to the data/sources that have been retrieved.                     

Experimental Web Platform Features

Background Fetch works if you have enabled ” Experimental Web Platform features” via the URL:

chrome://flags/                                   

Enabling Background Fetch

Enabling Background Fetch

Below is an example of how to implement such a Background Fetch.

Add this button with an ID of “bgFetchButton” in your index.html file (among the other buttons in the header).

Then, add the code for executing a Background Fetch in your app.js in the load event handler:

The code above performs a Background Fetch under the following conditions:

  • The user clicks on the button with the ID of bgFetchButton (the onClick event will go off)
  • The SW must be registered.

This check and the Background Fetch takes place within an async function because this process must be performed asynchronously without blocking the user.

Fill the cache in sw.js

This code consists of the following steps:

  • Once the Background Fetch retrieval is complete, your SW will receive the Background Fetch success event.
  • Create and open a new cache with the same name as the registration.id.
  • Get all records through registration.matchAll().
  • Build an array in an asynchronous way with promises by going through the records. Wait until the records with responses are ready and then save these responses in the cache with cache.put() (see the Cache Storage in the Application tab).
  • Finally, execute all the promises, through Promise.all().

Final output

Final output

Conclusion 

After this introduction, you can continue with an extensive tutorial that you can find in: https://github.com/petereijgermans11/progressive-web-app/tree /master/pwa-workshop. This tutorial focuses on issues such as generating your SW through Workbox, Caching strategies, Web Push Notifications, and Background synchronization with an IndexedDB API and various other new Web APIs.

Further Reading



Source link

Need help with Flexbox styling
Strategy

Need help with Flexbox styling


Need help with Flexbox styling

https://codepen.io/conye9980/pen/zYGrWpw?editors=1111

Hi,

Trying to sort out these 4 responsive blocks, seems to be working fine when choosing devices for mobile. However on ipad I am getting the following

https://preview.redd.it/k2pql5m41vg41.png?width=1100&format=png&auto=webp&s=113619bc3a0faea0ce78cb3f24448810fbede1c4

I have tried using Flex: shrink etc but doesn't seem to have the effect. Basically looking to keep the totals in the box.

submitted by /u/Vercetti86
[comments]



Source link

Post image
Strategy

Help getting a height-responsive div with CSS grid/Flexbox :…


I’m struggling to build a UI which has a div in the centre with a responsive height. Meaning that the divs above and below it stay at the top and bottom, while the central div is free to expand/contract depending on device height.

Post image

Hoping for something like this

I’m essentially looking for a space-between where I can mush stuff into the space-betweeny bit. Would anyone be able to help me out?



Source link

I made a table in which I want to have info about which browsers block 3rd party cookies and fingerprint by default
Strategy

I made a table in which I want to have info about which brow…


I made a table in which I want to have info about which browsers block 3rd party cookies and fingerprint by default

I completed as far as I could. Will some good soul help complete the rest of the table?
Browsers names and % of users using the browser I took from Google Analytics

https://preview.redd.it/jmngcl474vg41.png?width=1004&format=png&auto=webp&s=066045420f5a5570b97be3e17ee52a7c80598bbb

submitted by /u/gordriver_berserker
[comments]



Source link