Giter VIP home page Giter VIP logo

tabster's Introduction

Tabster

Tabindex on steroids.

A set of tools and concepts for making a dynamic web application properly accessible and keyboard-navigable.

https://tabster.io.

About

The way a browser and the screen readers handle a web application is evolved from the static web era. A process of making a modern dynamic web application accessible presents a number of challenges like, for example, the proper focus management between modal dialogs, popups, lists and other parts of the dynamically changing application. This project is an attempt to solve some of those challenges.

Dependencies

This project is framework-agnostic. It operates on the DOM level and has no external runtime dependencies. Though it is possible that your framework or application might have own logic to achieve similar result, in that case runtime conflicts and behavioural inconsistencies are definitely possible. At the same time, it does not do things automatically and parts of it should be explicitly enabled.

Parts

Focusable

An API for traversing focusable elements.

Deloser

When you remove, for example, a button which has focus from the DOM, the focus gets lost which is confusing for the screen reader and keyboard navigation users. Deloser is a concept which helps to automatically restore the focus when it gets lost without manually calling .focus() method from the application code.

FocusedElementState

An event and a couple of methods to track and change currently focused element.

KeyboardNavigationState

An event and a method to determine if the user is using keyboard to navigate through the application.

Groupper

Keyboard navigation for the lists should allow to avoid going through every list item when the users use Tab key (only one item of the list should be tabbable), also the arrow keys and Home/End/PageUp/PageDown keys should be handled to move between the list items. This is an API to easily make properly behaving lists.

Modalizer

When you show, for example, a modal dialog, the rest of the application might need to be excluded from the keyboard and screen reader navigation flow. Modalizer is a concept to conveniently make that possible.

Outline

When people navigate with the keyboard, the currently focused element should be properly highlighted. There is a CSS property called outline, which is unfortunately insufficient: the outline of an element gets cropped when a parent element has overflow: hidden, there is no way to limit the outline visibility to only the cases when the user is navigating with keyboard. So, we have a custom outline component which is supposed to solve both of the problems.

Contributing

Contributions are welcome (see the CONTRIBUTING file), though please keep in mind the work-in-progress proof-of-concept state. Might make sense to just observe/discuss until the thing gets stable and well-documented.

The repo now has an examples project powered by Storybook. Just run npm start

License

This project is licensed under the MIT License, see the LICENSE file for details.

tabster's People

Contributors

bryanvalverdeu avatar bsunderhus avatar dannyvv avatar dependabot[bot] avatar george-cz avatar jurokapsiar avatar layershifter avatar lgoeckener avatar ling1726 avatar microsoftopensource avatar msftgits avatar mshoho avatar notwoods avatar smhigley avatar spmonahan avatar yuanboxue-amber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tabster's Issues

aria-hidden-focus rule is broken by TabsterDummyInput element

While testing our product that uses tabster, the Accessibility Insights fast pass failed because the aria-hidden-focus rule is being broken by one of tabster's components. Our component is a Toolbar that uses tabster to enable circular arrow navigation in the toolbar, and the element breaking this rule is a that gets added above and below the children of the toolbar.

According to the rule, elements that have "aria-hidden" = true should not be focusable. The element has aria-hidden=true and tabindex=0, which are contradictory. Looks like this is being set here:

input.setAttribute(Types.TabsterDummyInputAttributeName, "");
.

Link to the broken rule: https://accessibilityinsights.io/info-examples/web/aria-hidden-focus/

Screen Reader announcing "button collapsed" for tabster dom element

Hello,
We are using tabster in our project and it seems that tabster is adding a dom element at the bottom of the page which causes the screen reader to read as "button collapsed".

Screenshot for the DOM element being rendered:
Code snippet _ Screen reader is announcing as button collapsed

Screen Recording for the occurring issue:

Screen.reader.is.announcing.as.button.collapsed.but.visually.no.button.is.present.1.mp4

Additional Notes:
I made sure that the mentioned DOM element added by tabster is causing the issue by deleting the element from DOM (from the dev tools) and the screen reader does not read "button collapsed".

Do let me know if more information is needed from my side.

Tabster causing costly recalculate styles when appending DOM elements to body

The origin for the issue is based on a lazy loaded component that adds an aria live region in the body of Teams.

To reproduce the issue you can start the latest version of Teams and start a performance trace.

Then run the following code in the console.
document.body.appendChild((() => { e = document.createElement("div"); e.innerText = "Hello world!"; return e; })())

Stop the performance trace, and you should see something like this:
image

The 111ms is done on a fairly new desktop computer, with 4x slowdown the time to compute is more than 500ms.

The reason for the recalculate style is tabster, and specifically within

private _addDummyInputs() {

When the body gets the new child appended, tabsters timeout will later run, and move the <i> element to the bottom of the body. This causes the recalculate style to happen.

Groupper: Default focus flow is still affected when using no params

ezgif com-gif-maker (1)

Above is a GIF showing the focus cycle, starting with only Tab pressing and then Shift + Tab when it reaches the last element.
As shown, element traversing works normally with Tab but not in the reverse direction with Shift + Tab as, when using the latter, focus is going to the parent element only instead of the inner element.

Here's the code:

const Card = () => {
  getGroupper(tabster);

  const tabsterAttrs = getTabsterAttribute({
    groupper: {
      tabbability: undefined
    }
  });

  return (
    <div tabIndex={0} {...tabsterAttrs}>
      <Button tabIndex={0}>Cool button</Button>
    </div>
  );
};

https://codesandbox.io/s/card-focus-example-61tbjw?file=/src/App.jsx

From the documentation, I couldn't understand if this is an unintended side-effect of focus trapping or intended behavior. Can you please clarify?

[Feature]: Support `aria-labelledby` for Modalizers

Current Behavior

Right now, Modalizers MUST have a aria-label attribute. otherwise tabster will console.warn:

🚨 Modalizer element must have aria-label

MicrosoftTeams-image

According to ARIA specs both aria-labelledby and aria-label can be used for labelling an element in a valid way.

Proposal

Allow Modalizers to have aria-label OR aria-labelledby as required attribute.

Performance tests

Currently there are a lot of hypotheses around the design of tabster for performance, none of them are actually concrete since we have no data to really compare against.

Cannot read properties of undefined (reading 'mover') when clicks on tab item

We are using the tablist component from @fluentui/react-components version 9.0.1. @fluentui/react-tabster requires tabster@^1.4.2 (package.json); note the caret which permits upgrades to minor versions.

We start seeing the following error after upgrading tabster to 1.5.1.
image

This error happens when a tab item is clicked. This is due to the useTabster hook in @fluentui calls the getCurrentTabster or createTabster methods.

export const useTabster = (): TabsterTypes.TabsterCore | null => {
  ...
  return getCurrentTabster(defaultView) ?? createTabster(defaultView, tabsterOptions);
};

The getCurrentTabster returns an instance of TabsterCore and the createTabster returns an instance of Tabster. createTabster() method assigning TabsterCore instance to the windows object instead of the Tabster instance. See code below:

/**
 * Creates an instance of Tabster, returns the current window instance if it already exists.
 */

function createTabster(win, props) {
  let tabster = getCurrentTabster(win);

  if (tabster) {
    return tabster.createTabster();
  }

  tabster = new TabsterCore(win, props);
  win.__tabsterInstance = tabster;
  return tabster.createTabster();
}

When the getMover method is trying to access tabster.core, tabster.core is undefined.

Proposal change: assigning Tabster instance to windows and return it.

function createTabster(win, props) {
  ...
  let tabsterCore = new TabsterCore(win, props);
  tabster = tabsterCore.createTabster();
  win.__tabsterInstance = tabster;
  return tabster;
}

Another proposal change in Fluent UI side, change the return in the useTabster hook to call createTabster method only.

export const useTabster = (): TabsterTypes.TabsterCore | null => {
  ...
  return createTabster(defaultView, tabsterOptions);
};

Root.ts uses EventTarget constructor which is not supported in older browsers

Root.ts is using a call to new EventTarget in the following line:

this.eventTarget = new EventTarget();

But the EventTarget constructor is not supported in multiple older browser versions:

https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/EventTarget

A possible workaround is to try to create the EventTarget and if that fails use an HTML element for it:

/**
 * Creates an EventTarget object supported by the current browser.
 */
export function createEventTarget(): EventTarget {
  // EventTarget constructor is not supported in IE or Safari, but HTML element implements the interface with small overhead.
  try {
    const target = new EventTarget();
    return target;
  } catch (err) {
    return document.createElement('div');
  }
}

Rethink Context

Make Tabster context more understandable (from reading the code perspective) and easier to work with (from the consumption perspective).

Race condition between deloser creation and focusing

Repro: https://codesandbox.io/s/priceless-meninsky-wi9rje?file=/example.tsx

  1. Focus 'Root trigger' button
  2. Press Enter
  3. 'Open popover' button should be focused
  4. Press Enter
  5. Press Escape
  6. Focus is on the 'Root trigger' button

The problem here is that once the popover opens, the first focusable element (which is a deloser) is focused before the deloser can be created.

This means that the deloser on the first button can never be added to the deloser history

tabster/src/Deloser.ts

Lines 173 to 180 in 3c29b31

process(element: HTMLElement): Types.Deloser | undefined {
const ctx = RootAPI.getTabsterContext(this._tabster, element);
const rootUId = ctx && ctx.root.uid;
const deloser = DeloserAPI.getDeloser(this._tabster, element);
if (!rootUId || !deloser) {
return undefined;
}

To follow the happy path and avoid the race condition, simply tab to the next button after 'Open popover' in steap 2 and shift tab back to 'Open popover'. This time the deloser is already created so the process will add it to deloser history

Groupper: reset focus trap when focus leaves the group

This bug can be found using the following steps (reproducible in the storybook for groupper):

  1. tab to the focus group container
  2. hit enter to reach the first focusable button
  3. click outside the focus group to move focus outside of it
  4. tab back to the focus group, and observe that tab reaches the inner buttons & is then trapped within the group without needing to hit enter again

The desired behavior would be that if focus leaves a focus group without using Escape (e.g. through clicking elsewhere, using a screen reader's virtual cursor, or mixing modes in any of several other ATs), the focus group state is reset and the user would need to hit enter again to reach inner items if the tabability is set to limited or limited-tab-focus.

Best Practices for Testing Focus Behavior

Hello Tabster Development Team,

I am currently exploring the use of Tabster in the context of web accessibility, with a specific focus on ensuring a seamless experience for users with accessibility needs, such as those utilizing screen readers.

As I navigate through this learning process, I recognize the importance of properly implementing and testing focus behavior, a key component in accessible web design. To that end, I am reaching out for guidance and best practices in this area.

Could you kindly provide specific recommendations on methodologies, tools, or resources effective for testing focus behavior in web projects? Are there particular tools or methods the Tabster team uses to ensure accurate focus behavior in real-world contexts, especially with manual testing using screen readers?

Additionally, I believe it would be beneficial if the Tabster documentation could include more information or links to resources about this topic.

Tabster async autoroot initialization breaks certain Fluent scenarios

This happened during the Modalizer revamp: Modalizer revamp by mshoho · Pull Request #218 · microsoft/tabster (github.com)

 

Reported issue: [Bug]: dialog directly mounted with open=true could not get focus · Issue #26824 · microsoft/fluentui (github.com)

Repro (also in the bug): Sandbox - CodeSandbox (csb.app)

 

What seems to be happening is that since [email protected] is that the autoroot creation is now async. This means that when a Fluent component that uses tabster is mounted, it generates errors because only the first usage of tabster initialized the autoroot. Since this is async, the React component can't actually use tabster on first render.

 

In the repro:

  • The dialog is mounted
  • tabster does not exist
  • dialog initializes tabster
  • dialog uses modalizer
  • tabster error -> no autoroot created
  • tabster creates autoroot
  • modalizer is inactive because there was not root
  • can't tab any element in the dialog
  • use the console to call .focus() on an element
  • now the modalizer is active

Error: 'using disposed tabster'.

We are using tabster version - 4.1.1

We are seeing many 'using disposed tabster' errors in our product logs.
Callstack -
image

I looked at the code and this could be causing issue.
https://github.com/microsoft/tabster/blame/c1b46b478fd0d75fe95b5653d525658e2bf16d6a/src/Modalizer.ts#:~:text=this._hiddenUpdateTimer%20%3D,%2C%20250)%3B

There is a possibility that this code could be executed after tabster is disposed, which would cause this error.

    this._hiddenUpdateTimer = this._win().setTimeout(() => {
        delete this._hiddenUpdateTimer;
        this._hiddenUpdate();
    }, 250);

@mshoho

Mover: have symmetrical forwards/backwards tabbing behavior

Currently the behavior for mover is to have tab focus land on the first item when tabbing forwards, and the last item when shift+tabbing backwards (reproducible in the storybook: https://tabster.io/storybook/?path=/story/mover--circular, applicable to all stories apart from the Tabbable one).

One of the principles of good tab navigation is that it is symmetrical when moving both forwards and backwards, so we should ensure tabster doesn't break this by default. My guess would be that the first item in the group should be the default focus stop, unless overridden by an author.

(feat) Restorer: investigate allowing child removal to trigger focus restoration in Safari & Firefox

Related to #305 -- with the fix in #306, Restorer will work in Safari & Firefox if the root node itself is removed from the DOM, but not if a child node containing document.activeElement is removed (note - CSS changes like display: none will still work, only DOM removal is an issue).

This seems like more of a minor use case/feature to support, since the primary use case is likely removing the root node. This issue is for tracking & to not lose the edge case after #306 is closed. Discussed w/ @ling1726

Mover: should recognize `isDefault`attribute from Focusable API

Focusable API has supported the isDefault attribute for a while now for Deloser scenarios. This should be extended to work with the Mover API.

The Mover API should also support an optional prop so that it should look for default focusables, this will avoid conflicts with other features such as `memorizeCurrent

Add Bundle size metrics

As bundle size is critical for this project, we should add bundle size metrics and reports to keep a better track on this. The bundle-size used internally on FluentUI should be enough.

Verifying components relying on tabster through unit tests

Hey folks,

I am using Fluent 9 Popover and wanted to verify that correct element is focused open opening the Popover. For the component I created, I can visually verify in Storybook that correct element is focused. However, when I want to make such assertion in unit tests, it seems to no longer be the case. Can tabster functionality be somehow verified in jest and jsdom environment? Or the only way to verify is to have e2e tests, rendering browser window?

`RadioGroup` tab behavior incorrect when using `useFocusableGroup`

Expected Behavior

When using Fluent UI v9 RadioGroup with react-tabster's useFocusableGroup RadioGroup native tabbing and keyboard behavior should be preserved.

Observed Behavior

When using useFocusableGroup with RadioGroup the native tabbing and keyboard behavior is overridden in unexpected ways.

In particular, with Tabster:

  1. Each option in RadioGroup is a tab stop (i.e., tab presses move between the options) when the RadioGroup should be a single tab stop.
    1. Options are only supposed to be navigable with the arrow keys when using the keyboard. Arrow keys still work with Tabster.
  2. When RadioGroup has a selected option tabbing or shift-tabbing to the RadioGroup from a different focusable element focuses the first or last option in the RadioGroup respectively. It should focus the selected option.

Example

Codesandbox Example

Related Issues

Fluent UI v8's ChoiceGroup had a similar issue with FocusTrapZone.

Tab stops on the last arrow navigation group

When we only have arrow navigable groups, the tabbing stops at the last group. We expect it to go out to the browser controls, or in the fullscreen case, cycle back to the first navigable group.

In this example: https://codesandbox.io/s/blissful-http-9r31z0?file=/example.tsx if you keep tabbing, you can see that the tabbing stops at the last group of arrow navigable group. Note: you need to open the example page itself to see this happening: https://9r31z0.csb.app/

To extra clarify: when we are at fullscreen, we expect that the first tab goes to the first group, the second tab goes to the second group, the third goes back to the first group and so on. But currently, the third and subsequent tabs will just stay on the last group.

I've tried exploring the useArrowNavigationGroup and useFocusableGroup hooks to see if there is something there we can use to achieve this, but no success.

Is this a bug? Any suggestions?

Mover ignores input from elements with `aria-expanded`

Problem description

When the useTableCompositeNavigation hook is used on a table, and when the aria-expanded attribute is applied on the table row, the arrow key navigation stops working.

Code example

import * as React from 'react';
import {
  useTableCompositeNavigation,
  Button,
} from '@fluentui/react-components';

export const TreeGrid: React.FC<IRecentMeetingsTreeRendererrerProps> = ({ recentMeetings }) => {
  const { tableRowTabsterAttribute, tableTabsterAttribute, onTableKeyDown } = useTableCompositeNavigation();

  return (
    <table
        role= "treegrid"
  {...tableTabsterAttribute }
      >
    <tbody>
    <tr
            role="row"
  tabIndex = { 0}
  aria - level={ 1 }
  aria - expanded={ true }
  {...tableRowTabsterAttribute }
          >
    <td
              role="gridcell"
  tabIndex = { 0}
  colSpan = { 4}
    > Meeting with manager < /td>
    < /tr>
    < tr
            role = "row"
  tabIndex = { 0}
  aria - level={ 2 }
  {...tableRowTabsterAttribute }
          >
    <td role="gridcell" tabIndex = { 0} > Re: meeting with manager < /td>
    < td role = "gridcell" > <Button>Agenda and notes < /Button></td >
      <td role="gridcell" > <Button>Chat with participants < /Button></td >
      <td role= "gridcell" > <Button>View recap < /Button></td >
        </tr>
        < tr
  role = "row"
  tabIndex = { 0}
  aria - level={ 2 }
  {...tableRowTabsterAttribute }
          >
    <td role="gridcell" tabIndex = { 0} > Re 2: meeting with manager < /td>
    < td role = "gridcell" > <Button>Agenda and notes < /Button></td >
      <td role="gridcell" > <Button>Chat with participants < /Button></td >
      <td role= "gridcell" > <Button>View recap < /Button></td >
        </tr>
        < /tbody>
        < /table>

  );
};

Cannot enter whitespace with "tab" key into monaco editor since around tabster v4.7.3

I'm not sure from which version this issue starts to manifest, but tabster v4.7.0 definitely does not have such issue.

Reproduction (CodeSandbox): https://codesandbox.io/s/fluent-ui-v9-tabster-with-monaco-editor-ldwt64?file=/src/example.tsx

When I press "tab" key inside monaco editor, I expect to either

  • see 4 spaces appears in the editor,
  • or accept the item from the auto-completion dropdown.

but instead my focus gets moved to the auto-completion box, or the button.

Recording_20231031T171522

Support RTL for Mover API

AH needs extra configuration to be RTL aware so that mover can know when to flip left/right arrow keys

AH needs to be able to configure RTL state at runtime

getModalizer backward incompatibility issue

Our product is micro-frontend alike architecture, there's a host UI project and several guest UI projects that are all built and shipped differently, and all the dependencies are not shared. We discovered an issue during the upgrade of FluentUI v9, which is caused by different versions of tabster in host UI side and guest UI side.

A minimal repro could be found here: https://github.com/KagamiChan/multiple-fluentui-repro
I'm not using codesandbox because I need to use yarn resolutions to lock tabster version, please let me know if you prefer a codesandbox example.

tabster versions:
Host UI: 4.2.0
Guest UI: 4.6.0
main difference for this issue: tabster 4.3.0 introduces queue for initialization: #262

Steps to repro:

  • click the button to mount guest UI
  • error throws like below
image

what happened:

  1. host UI is mounted, and tabster core (v4.2.0) is initialized, while modalizer is not
  2. guest UI trying to mount, FluentUI tries to initialize modalizer, the getModalizer function and ModalizerAPI class is from guest UI (v4.6.0) so it calls tabster isntance's queueInit function
  3. tabster instance is initialized by host UI (v4.2.0) and does not have queueInit function, the exception throws

This issue is blocking us from the Fluent UI upgrade because we have to upgrade host UI everywhere but that would be difficult (our product is not served on web but is embedded), could you please help take a look and help us unblock?

`getCurrentTabster` is not exported in 1.5.0, breaking dependents

@fluentui/react-tabster requires tabster@^1.4.2 (package.json); note the caret which permits upgrades to minor versions.

Fresh installs are failing because the latest [email protected] doesn’t export getCurrentTabster (index.ts) which @fluentui/react-tabster imports in useTabster.

Failed to compile.

./node_modules/@fluent-blocks/react/node_modules/@fluentui/react-tabster/lib/hooks/useTabster.js
Attempted import error: 'getCurrentTabster' is not exported from 'tabster'.

Since 1.5.0 is a breaking change, I think it should be released as 2.0.0 if the change was intentional. I think a patch should be released as 1.5.1 to restore getCurrentTabster so folks’ builds don’t break, or dependents should specify only patches with ~ or use a specific version in the meantime. As an indirect consumer of tabster, I have to override the tabster version dependency using our project’s package manager.

Mover: do not store all mover state in a static variable

Problem

Currently mover stores all instances of itself as a static class member

private static _movers: Record<string, Mover> = {};

This is bad since the dispose of the actual Mover instance is only called when a DOM change is detected by the mutation observer. In scenarios where there is a child window, closing the window will not trigger mutation observer events and dispose is never called.

Therefore all Mover instances are retained int he static member.

Solution

Refactor Mover and MoverAPI so that the collection of Mover instances is managed by MoverAPI so it can be safely disposed by consuming apps, by disposing APIs through disposeTabster.

This will mean that updateVisible will need to be hoisted from Mover to MoverAPI

Make mover `hasDefault` option true by default

Currently mover has hasDefault option, when it is true, mover looks for the focusable item that has focusable.isDefault attribute. Would be nice to make this option default true instead.

Deloser: support rendered but not visible modals

Current behavior

At the moment, deloser attributes assume a modal is opened when it's added to the DOM and is closed when it's removed from the DOM, returning the focus to the previous deloser once the modal is closed.

This is not ideal for native modals such as <dialog> since the element is always present at the DOM and it's visibility is dictated by display: none

reproduction

Requested change

Allow deloser to identify the element is not focusable anymore in the DOM although it's still present, therefore allowing return of the focus to the previous deloser.

Add possibility to register custom callback when key events occur in tabster

Feture request

There are scenarios when the developer wants to complement or override key events handled by tabster (such as Right arrow key press) using a custom code that is executed only under some circumstances, such as when the keyboard focus is on some specific element. This need can be resolved by introducing the possibility to register a custom callback for the key events which are handled by tabster. Then, running event.preventDefault() in such callback can block the execution of the default tabster handler.

Restorer relies on focusout event firing when element is removed

Safari does not fire focusout or any other events when a node is removed, which means focus isn't restored when the restorer source is unmounted, even though focus returns to the body.

This can be reproduced in the Restorer stories in Safari, and also in FUI in components with popups (e.g. Popover, Dialog, Menu, etc.).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.